Skip to content

Most visited

Recently visited

navigation
XYZTouristAttractions / Application / src / com.example.android.xyztouristattractions / service /

UtilityService.java

1
/*
2
 * Copyright 2015 Google Inc. All rights reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.xyztouristattractions.service;
18
 
19
import android.app.IntentService;
20
import android.app.Notification;
21
import android.app.PendingIntent;
22
import android.content.Context;
23
import android.content.Intent;
24
import android.content.IntentFilter;
25
import android.graphics.Bitmap;
26
import android.location.Location;
27
import android.support.v4.app.NotificationCompat;
28
import android.support.v4.app.NotificationManagerCompat;
29
import android.support.v4.content.LocalBroadcastManager;
30
import android.util.Log;
31
 
32
import com.bumptech.glide.Glide;
33
import com.bumptech.glide.load.engine.DiskCacheStrategy;
34
import com.example.android.xyztouristattractions.R;
35
import com.example.android.xyztouristattractions.common.Attraction;
36
import com.example.android.xyztouristattractions.common.Constants;
37
import com.example.android.xyztouristattractions.common.Utils;
38
import com.example.android.xyztouristattractions.provider.TouristAttractions;
39
import com.example.android.xyztouristattractions.ui.DetailActivity;
40
import com.google.android.gms.common.ConnectionResult;
41
import com.google.android.gms.common.api.GoogleApiClient;
42
import com.google.android.gms.location.FusedLocationProviderApi;
43
import com.google.android.gms.location.Geofence;
44
import com.google.android.gms.location.GeofencingEvent;
45
import com.google.android.gms.location.LocationRequest;
46
import com.google.android.gms.location.LocationServices;
47
import com.google.android.gms.maps.model.LatLng;
48
import com.google.android.gms.wearable.DataApi;
49
import com.google.android.gms.wearable.DataMap;
50
import com.google.android.gms.wearable.PutDataMapRequest;
51
import com.google.android.gms.wearable.PutDataRequest;
52
import com.google.android.gms.wearable.Wearable;
53
 
54
import java.util.ArrayList;
55
import java.util.Date;
56
import java.util.HashMap;
57
import java.util.Iterator;
58
import java.util.List;
59
import java.util.concurrent.ExecutionException;
60
import java.util.concurrent.TimeUnit;
61
 
62
import static com.example.android.xyztouristattractions.provider.TouristAttractions.ATTRACTIONS;
63
import static com.google.android.gms.location.LocationServices.FusedLocationApi;
64
import static com.google.android.gms.location.LocationServices.GeofencingApi;
65
 
66
/**
67
 * A utility IntentService, used for a variety of asynchronous background
68
 * operations that do not necessarily need to be tied to a UI.
69
 */
70
public class UtilityService extends IntentService {
71
    private static final String TAG = UtilityService.class.getSimpleName();
72
 
73
    public static final String ACTION_GEOFENCE_TRIGGERED = "geofence_triggered";
74
    private static final String ACTION_LOCATION_UPDATED = "location_updated";
75
    private static final String ACTION_REQUEST_LOCATION = "request_location";
76
    private static final String ACTION_ADD_GEOFENCES = "add_geofences";
77
    private static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
78
    private static final String ACTION_CLEAR_REMOTE_NOTIFICATIONS = "clear_remote_notifications";
79
    private static final String ACTION_FAKE_UPDATE = "fake_update";
80
    private static final String EXTRA_TEST_MICROAPP = "test_microapp";
81
 
82
    public static IntentFilter getLocationUpdatedIntentFilter() {
83
        return new IntentFilter(UtilityService.ACTION_LOCATION_UPDATED);
84
    }
85
 
86
    public static void triggerWearTest(Context context, boolean microApp) {
87
        Intent intent = new Intent(context, UtilityService.class);
88
        intent.setAction(UtilityService.ACTION_FAKE_UPDATE);
89
        intent.putExtra(EXTRA_TEST_MICROAPP, microApp);
90
        context.startService(intent);
91
    }
92
 
93
    public static void addGeofences(Context context) {
94
        Intent intent = new Intent(context, UtilityService.class);
95
        intent.setAction(UtilityService.ACTION_ADD_GEOFENCES);
96
        context.startService(intent);
97
    }
98
 
99
    public static void requestLocation(Context context) {
100
        Intent intent = new Intent(context, UtilityService.class);
101
        intent.setAction(UtilityService.ACTION_REQUEST_LOCATION);
102
        context.startService(intent);
103
    }
104
 
105
    public static void clearNotification(Context context) {
106
        Intent intent = new Intent(context, UtilityService.class);
107
        intent.setAction(UtilityService.ACTION_CLEAR_NOTIFICATION);
108
        context.startService(intent);
109
    }
110
 
111
    public static Intent getClearRemoteNotificationsIntent(Context context) {
112
        Intent intent = new Intent(context, UtilityService.class);
113
        intent.setAction(UtilityService.ACTION_CLEAR_REMOTE_NOTIFICATIONS);
114
        return intent;
115
    }
116
 
117
    public UtilityService() {
118
        super(TAG);
119
    }
120
 
121
    @Override
122
    protected void onHandleIntent(Intent intent) {
123
        String action = intent != null ? intent.getAction() : null;
124
        if (ACTION_ADD_GEOFENCES.equals(action)) {
125
            addGeofencesInternal();
126
        } else if (ACTION_GEOFENCE_TRIGGERED.equals(action)) {
127
            geofenceTriggered(intent);
128
        } else if (ACTION_REQUEST_LOCATION.equals(action)) {
129
            requestLocationInternal();
130
        } else if (ACTION_LOCATION_UPDATED.equals(action)) {
131
            locationUpdated(intent);
132
        } else if (ACTION_CLEAR_NOTIFICATION.equals(action)) {
133
            clearNotificationInternal();
134
        } else if (ACTION_CLEAR_REMOTE_NOTIFICATIONS.equals(action)) {
135
            clearRemoteNotifications();
136
        } else if (ACTION_FAKE_UPDATE.equals(action)) {
137
            LatLng currentLocation = Utils.getLocation(this);
138
 
139
            // If location unknown use test city, otherwise use closest city
140
            String city = currentLocation == null ? TouristAttractions.TEST_CITY :
141
                    TouristAttractions.getClosestCity(currentLocation);
142
 
143
            showNotification(city,
144
                    intent.getBooleanExtra(EXTRA_TEST_MICROAPP, Constants.USE_MICRO_APP));
145
        }
146
    }
147
 
148
    /**
149
     * Add geofences using Play Services
150
     */
151
    private void addGeofencesInternal() {
152
        Log.v(TAG, ACTION_ADD_GEOFENCES);
153
 
154
        if (!Utils.checkFineLocationPermission(this)) {
155
            return;
156
        }
157
 
158
        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
159
                .addApi(LocationServices.API)
160
                .build();
161
 
162
        // It's OK to use blockingConnect() here as we are running in an
163
        // IntentService that executes work on a separate (background) thread.
164
        ConnectionResult connectionResult = googleApiClient.blockingConnect(
165
                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
166
 
167
        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
168
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
169
                    this, 0, new Intent(this, UtilityReceiver.class), 0);
170
            GeofencingApi.addGeofences(googleApiClient,
171
                    TouristAttractions.getGeofenceList(), pendingIntent);
172
            googleApiClient.disconnect();
173
        } else {
174
            Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
175
                    connectionResult.getErrorCode()));
176
        }
177
    }
178
 
179
    /**
180
     * Called when a geofence is triggered
181
     */
182
    private void geofenceTriggered(Intent intent) {
183
        Log.v(TAG, ACTION_GEOFENCE_TRIGGERED);
184
 
185
        // Check if geofences are enabled
186
        boolean geofenceEnabled = Utils.getGeofenceEnabled(this);
187
 
188
        // Extract the geofences from the intent
189
        GeofencingEvent event = GeofencingEvent.fromIntent(intent);
190
        List<Geofence> geofences = event.getTriggeringGeofences();
191
 
192
        if (geofenceEnabled && geofences != null && geofences.size() > 0) {
193
            if (event.getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_ENTER) {
194
                // Trigger the notification based on the first geofence
195
                showNotification(geofences.get(0).getRequestId(), Constants.USE_MICRO_APP);
196
            } else if (event.getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_EXIT) {
197
                // Clear notifications
198
                clearNotificationInternal();
199
                clearRemoteNotifications();
200
            }
201
        }
202
        UtilityReceiver.completeWakefulIntent(intent);
203
    }
204
 
205
    /**
206
     * Called when a location update is requested
207
     */
208
    private void requestLocationInternal() {
209
        Log.v(TAG, ACTION_REQUEST_LOCATION);
210
 
211
        if (!Utils.checkFineLocationPermission(this)) {
212
            return;
213
        }
214
 
215
        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
216
                .addApi(LocationServices.API)
217
                .build();
218
 
219
        // It's OK to use blockingConnect() here as we are running in an
220
        // IntentService that executes work on a separate (background) thread.
221
        ConnectionResult connectionResult = googleApiClient.blockingConnect(
222
                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
223
 
224
        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
225
 
226
            Intent locationUpdatedIntent = new Intent(this, UtilityService.class);
227
            locationUpdatedIntent.setAction(ACTION_LOCATION_UPDATED);
228
 
229
            // Send last known location out first if available
230
            Location location = FusedLocationApi.getLastLocation(googleApiClient);
231
            if (location != null) {
232
                Intent lastLocationIntent = new Intent(locationUpdatedIntent);
233
                lastLocationIntent.putExtra(
234
                        FusedLocationProviderApi.KEY_LOCATION_CHANGED, location);
235
                startService(lastLocationIntent);
236
            }
237
 
238
            // Request new location
239
            LocationRequest mLocationRequest = new LocationRequest()
240
                    .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
241
            FusedLocationApi.requestLocationUpdates(
242
                    googleApiClient, mLocationRequest,
243
                    PendingIntent.getService(this, 0, locationUpdatedIntent, 0));
244
 
245
            googleApiClient.disconnect();
246
        } else {
247
            Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
248
                    connectionResult.getErrorCode()));
249
        }
250
    }
251
 
252
    /**
253
     * Called when the location has been updated
254
     */
255
    private void locationUpdated(Intent intent) {
256
        Log.v(TAG, ACTION_LOCATION_UPDATED);
257
 
258
        // Extra new location
259
        Location location =
260
                intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
261
 
262
        if (location != null) {
263
            LatLng latLngLocation = new LatLng(location.getLatitude(), location.getLongitude());
264
 
265
            // Store in a local preference as well
266
            Utils.storeLocation(this, latLngLocation);
267
 
268
            // Send a local broadcast so if an Activity is open it can respond
269
            // to the updated location
270
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
271
        }
272
    }
273
 
274
    /**
275
     * Clears the local device notification
276
     */
277
    private void clearNotificationInternal() {
278
        Log.v(TAG, ACTION_CLEAR_NOTIFICATION);
279
        NotificationManagerCompat.from(this).cancel(Constants.MOBILE_NOTIFICATION_ID);
280
    }
281
 
282
    /**
283
     * Clears remote device notifications using the Wearable message API
284
     */
285
    private void clearRemoteNotifications() {
286
        Log.v(TAG, ACTION_CLEAR_REMOTE_NOTIFICATIONS);
287
        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
288
                .addApi(Wearable.API)
289
                .build();
290
 
291
        // It's OK to use blockingConnect() here as we are running in an
292
        // IntentService that executes work on a separate (background) thread.
293
        ConnectionResult connectionResult = googleApiClient.blockingConnect(
294
                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
295
 
296
        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
297
 
298
            // Loop through all nodes and