package com.magnet.wru; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; import android.content.Context; import android.location.Location; import android.util.Log; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; import com.magnet.mmx.client.api.MMX; import com.magnet.mmx.client.api.MMXChannel; import com.magnet.mmx.client.api.MMXMessage; import com.magnet.mmx.protocol.GeoLoc; import java.util.Date; import java.util.Map; /** * An {@link IntentService} subclass for handling asynchronous task requests in * a service on a separate handler thread. * <p/> * helper methods. */ public class LocationUpdaterService extends IntentService { private static final String TAG = LocationUpdaterService.class.getSimpleName(); public static final String ACTION_UPDATE_LOCATION = "com.magnet.wru.action.UPDATE_LOCATION"; public static final String ACTION_LOCATION_UPDATED = "com.magnet.wru.action.LOCATION_UPDATED"; public static final String EXTRA_USERNAME = "com.magnet.wru.extra.USERNAME"; public static final String EXTRA_TOPIC_NAME = "com.magnet.wru.extra.TOPIC_NAME"; /** * Starts the location updates for the specified topic/username on the given interval. If * the service is already performing a task this action will be queued. * * @param context the context * @param username the username * @param topic the topic * @param updateInterval if >= 0 will perform a location update and schedule future updates on the specified interval */ public static void startLocationUpdates(Context context, String username, MMXChannel topic, long updateInterval) { if (updateInterval >= 0) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), updateInterval, getInvokeUpdatePendingIntent(context, username, topic)); } else { updateLocation(context, username, topic); } registerForLocationUpdates(context); } public static void updateLocation(Context context, String username, MMXChannel topic) { Intent intent = getInvokeUpdateIntent(context, username, topic); context.sendBroadcast(intent); } private static Intent getInvokeUpdateIntent(Context context, String username, MMXChannel topic) { Intent intent = new Intent(context, LocationUpdaterService.LocationBroadcastReceiver.class); intent.setAction(ACTION_UPDATE_LOCATION); intent.putExtra(EXTRA_USERNAME, username); intent.putExtra(EXTRA_TOPIC_NAME, topic.getName()); return intent; } private static PendingIntent getInvokeUpdatePendingIntent(Context context, String username, MMXChannel topic) { return PendingIntent.getBroadcast(context, 0, getInvokeUpdateIntent(context, username, topic), PendingIntent.FLAG_UPDATE_CURRENT); } private static Intent getLocationUpdatedIntent(Context context) { Intent intent = new Intent(context, LocationUpdaterService.LocationBroadcastReceiver.class); intent.setAction(ACTION_LOCATION_UPDATED); return intent; } private static PendingIntent getLocationUpdatedPendingIntent(Context context) { return PendingIntent.getBroadcast(context, 0, getLocationUpdatedIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); } /** * Stops the location updates. If * the service is already performing a task this action will be queued. * * @see IntentService */ public static void stopLocationUpdates(Context context, String username, MMXChannel topic) { Log.d(TAG, "stopLocationUpdates(): Stopping location updates"); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.cancel(getInvokeUpdatePendingIntent(context, username, topic)); unregisterForLocationUpdates(context); } public LocationUpdaterService() { super("LocationUpdaterService"); } @Override protected void onHandleIntent(Intent intent) { Log.d(TAG, "onHandleIntent(): start. Intent=" + intent); EventLog.getInstance(this).add(EventLog.Type.INFO, "LocationUpdaterService handling intent: " + intent); if (intent != null) { final String action = intent.getAction(); if (ACTION_UPDATE_LOCATION.equals(action)) { final String username = intent.getStringExtra(EXTRA_USERNAME); final String topicName = intent.getStringExtra(EXTRA_TOPIC_NAME); MMXChannel joinedTopic = WRU.getInstance(this).getJoinedTopic(); if (joinedTopic != null && joinedTopic.getName().equalsIgnoreCase(topicName)) { handleLocationUpdate(username, joinedTopic); } } else if (ACTION_LOCATION_UPDATED.equals(action)) { //this is the intent that is fired when the location has been updated from GooglePlayServices //need to do a publish LocationResult result = LocationResult.extractResult(intent); if (result != null) { Location lastLocation = result.getLastLocation(); WRU wru = WRU.getInstance(this); publishLocation(lastLocation, wru.getUsername(), wru.getJoinedTopic()); } } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { WRU wru = WRU.getInstance(this); if (wru.getJoinedTopic() != null) { registerForLocationUpdates(this); handleLocationUpdate(wru.getUsername(), wru.getJoinedTopic()); } } } } private void publishLocation(final Location location, final String username, final MMXChannel topic) { WRU wru = WRU.getInstance(this); final Date locationDate = new Date(location.getTime()); EventLog.getInstance(this).add(EventLog.Type.INFO, "Location: " + location); Log.d(TAG, "publishLocation(): publishing..."); final GeoLoc geo = new GeoLoc(); geo.setAccuracy((int) location.getAccuracy()); geo.setLat((float) location.getLatitude()); geo.setLng((float) location.getLongitude()); final Map<String, String> payload = WRU.buildPayload(username, geo, locationDate); if (MMX.getCurrentUser() == null) { Log.e(TAG, "publishLocation(): Not publishing because not connected."); EventLog.getInstance(LocationUpdaterService.this).add(EventLog.Type.ERROR, "Publish failed. Not connected."); } else { publish(topic, payload); } } private void publish(MMXChannel topic, Map<String, String> payload) { topic.publish(payload, new MMXMessage.OnFinishedListener<String>() { public void onSuccess(String s) { Log.d(TAG, "publish(): successfully published location"); } public void onFailure(MMXMessage.FailureCode failureCode, Throwable throwable) { Log.e(TAG, "publish(): Unable to publish location: " + failureCode, throwable); EventLog.getInstance(LocationUpdaterService.this).add(EventLog.Type.ERROR, "Publish failed: " + failureCode); } }); } /** * Handle start location updates in the provided background thread with the provided * parameters. */ private void handleLocationUpdate(final String username, final MMXChannel topic) { Log.d(TAG, "handleLocationUpdate(): start"); GoogleApiClient googleApiClient = WRU.getInstance(this).waitForGoogleApi(); Location currentLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient); if (currentLocation == null) { Log.e(TAG, "handleLocationUpdate(): Unable to retrieve location from locationClient. " + "Ensure that the proper permissions(android.permission.ACCESS_COARSE_LOCATION, " + "android.permission.ACCESS_FINE_LOCATION) have been declared in the " + "AndroidManifest.xml file. Skipping..."); } else { publishLocation(currentLocation, username, topic); Log.d(TAG, "handleLocationUpdate(): completed."); } } private static void registerForLocationUpdates(final Context context) { GoogleApiClient googleApiClient = WRU.getInstance(context).waitForGoogleApi(); Log.d(TAG, "registerForLocationUpdates(): requesting location updates from FusedLocationApi"); LocationRequest request = new LocationRequest() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setSmallestDisplacement(10) // to prevent unnecessary updates if we haven't moved 10m .setInterval(5 * 60 * 60 * 1000) // 5 minutes .setFastestInterval(5000) // 5 seconds ; PendingResult<Status> result = LocationServices.FusedLocationApi .requestLocationUpdates(googleApiClient, request, getLocationUpdatedPendingIntent(context)); Log.d(TAG, "registerForLocationUpdates(): result=" + result); } private static void unregisterForLocationUpdates(final Context context) { GoogleApiClient googleApiClient = WRU.getInstance(context).waitForGoogleApi(); Log.d(TAG, "unregisterForLocationUpdates(): removing location updates from FusedLocationApi"); PendingResult<Status> result= LocationServices.FusedLocationApi .removeLocationUpdates(googleApiClient, getLocationUpdatedPendingIntent(context)); } public static final class LocationBroadcastReceiver extends BroadcastReceiver { private static final String TAG = LocationBroadcastReceiver.class.getSimpleName(); public void onReceive(Context context, Intent intent) { //TODO: Handle boot completed intent as well Log.d(TAG, "onReceive(): intent=" + intent); intent.setComponent(new ComponentName(context, LocationUpdaterService.class)); context.startService(intent); } } }