Java tutorial
/* Copyright (c) 2014-2015 F-Secure See LICENSE for details */ package cc.softwarefactory.lokki.android.androidServices; import android.app.AlarmManager; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.location.Location; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.PowerManager; import android.os.SystemClock; import android.os.Vibrator; import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import org.json.JSONException; import cc.softwarefactory.lokki.android.MainApplication; import cc.softwarefactory.lokki.android.R; import cc.softwarefactory.lokki.android.activities.BuzzActivity; import cc.softwarefactory.lokki.android.activities.MainActivity; import cc.softwarefactory.lokki.android.models.Place; import cc.softwarefactory.lokki.android.services.PlaceService; import cc.softwarefactory.lokki.android.utilities.PreferenceUtils; import cc.softwarefactory.lokki.android.utilities.ServerApi; import cc.softwarefactory.lokki.android.utilities.Utils; import cc.softwarefactory.lokki.android.utilities.map.MapUtils; public class LocationService extends Service implements LocationListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { // INTERVALS /** * Location polling interval when the service is set to get high accuracy updates (App running in foreground) */ private static final long LOCATION_CHECK_INTERVAL_ACCURATE = 5000; //5 seconds /** * Location polling interval when the service is set to get medium accuracy updates (App running in background with Location Buzz) */ private static final long LOCATION_CHECK_INTERVAL_BACKGROUND_ACCURATE = 15000; //15 seconds /** * Location polling interval when the service is set to get low accuracy updates (App running in background without Location Buzz) */ private static final long LOCATION_CHECK_INTERVAL_BACKGROUND_INACCURATE = 1000 * 60 * 15; //15 minutes private static final long INTERVAL_30_SECS = 30 * 1000; private static final long INTERVAL_1_MIN = 60 * 1000; // - SERVICE private static final int NOTIFICATION_SERVICE = 100; // OTHER private static final String TAG = "LocationService"; private static final String RUN_1_MIN = "RUN_1_MIN"; private static final String ALARM_TIMER = "ALARM_TIMER"; private GoogleApiClient mGoogleApiClient; /** * Location request used to request accurate foreground updates from the Location API */ private LocationRequest locationRequestAccurate; /** * Location request used to request accurate background updates from the Location API */ private LocationRequest locationRequestBGAccurate; /** * Location request used to request inaccurate background updates from the Location API */ private LocationRequest locationRequestBGInaccurate; private static Boolean serviceRunning = false; private static Location lastLocation = null; private PowerManager.WakeLock wakeLock; /** * Current accuracy of location updates */ private LocationAccuracy currentAccuracy = LocationAccuracy.BGINACCURATE; private static PlaceService placeService; /** * Location polling accuracy levels: * ACCURATE: App running in foreground * BGACCURATE: App running in background, but still needs relatively high accuracy for e.g. Location Buzz * BGINACCURATE: App running in background, low accuracy */ public enum LocationAccuracy { ACCURATE, BGACCURATE, BGINACCURATE } public static void start(Context context) { Log.d(TAG, "start Service called"); placeService = new PlaceService(context); if (serviceRunning) { // If service is running, no need to start it again. Log.w(TAG, "Service already running..."); return; } context.startService(new Intent(context, LocationService.class)); } public static void stop(Context context) { Log.d(TAG, "stop Service called"); context.stopService(new Intent(context, LocationService.class)); } public static void run1min(Context context) { if (serviceRunning || !MainApplication.visible) { return; // If service is running or user is not visible, stop } Log.d(TAG, "run1min called"); Intent intent = new Intent(context, LocationService.class); intent.putExtra(RUN_1_MIN, 1); context.startService(intent); } @Override public void onCreate() { Log.d(TAG, "onCreate"); super.onCreate(); if (PreferenceUtils.getString(this, PreferenceUtils.KEY_AUTH_TOKEN).isEmpty()) { Log.d(TAG, "User disabled reporting in App. Service not started."); stopSelf(); } else if (Utils.checkGooglePlayServices(this)) { Log.d(TAG, "Starting Service.."); setLocationClient(); setNotificationAndForeground(); PowerManager mgr = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE); wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GPS Child Location Wake Lock"); wakeLock.acquire(); serviceRunning = true; } else { Log.e(TAG, "Google Play Services Are NOT installed."); stopSelf(); } } private void setTemporalTimer() { AlarmManager alarm = (AlarmManager) getSystemService(ALARM_SERVICE); Intent alarmIntent = new Intent(this, LocationService.class); alarmIntent.putExtra(ALARM_TIMER, 1); PendingIntent alarmCallback = PendingIntent.getService(this, 0, alarmIntent, 0); alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + INTERVAL_1_MIN, alarmCallback); Log.d(TAG, "Time created."); } private void setLocationClient() { //Build location requests for different power profiles locationRequestAccurate = LocationRequest.create(); locationRequestAccurate.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); locationRequestAccurate.setInterval(LOCATION_CHECK_INTERVAL_ACCURATE); locationRequestBGAccurate = LocationRequest.create(); locationRequestBGAccurate.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); locationRequestBGAccurate.setInterval(LOCATION_CHECK_INTERVAL_BACKGROUND_ACCURATE); locationRequestBGInaccurate = LocationRequest.create(); locationRequestBGInaccurate.setPriority(LocationRequest.PRIORITY_LOW_POWER); locationRequestBGInaccurate.setInterval(LOCATION_CHECK_INTERVAL_BACKGROUND_INACCURATE); //Create and connect to Google API client mGoogleApiClient = new GoogleApiClient.Builder(this).addApi(LocationServices.API) .addConnectionCallbacks(this).addOnConnectionFailedListener(this).build(); mGoogleApiClient.connect(); Log.d(TAG, "Location Client created."); } private void setNotificationAndForeground() { NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this); notificationBuilder.setContentTitle("GPS Child Location"); notificationBuilder.setContentText("Running..."); notificationBuilder.setSmallIcon(R.drawable.ic_stat_notify); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); notificationBuilder.setContentIntent(contentIntent); startForeground(NOTIFICATION_SERVICE, notificationBuilder.build()); } /** * Switched current location update accuracy level to prioritize accuracy or power saving * @param acc The new accuracy level */ public void setLocationCheckAccuracy(LocationAccuracy acc) { currentAccuracy = acc; if (mGoogleApiClient == null || !mGoogleApiClient.isConnected()) { Log.i(TAG, "Google API client not yet initialized, so not requesting updates yet"); return; } LocationRequest req; switch (acc) { case ACCURATE: { Log.d(TAG, "Setting location request accuracy to accurate"); req = locationRequestAccurate; break; } case BGACCURATE: { Log.d(TAG, "Setting location request accuracy to background accurate"); req = locationRequestBGAccurate; break; } case BGINACCURATE: { Log.d(TAG, "Setting location request accuracy to background inaccurate"); req = locationRequestBGInaccurate; break; } default: { Log.wtf(TAG, "Unknown location accuracy level"); throw new IllegalArgumentException("Unknown location accuracy level"); } } LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, req, this); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand invoked"); if (intent == null) { return START_STICKY; } Bundle extras = intent.getExtras(); if (extras == null) { return START_STICKY; } if (extras.containsKey(RUN_1_MIN)) { Log.d(TAG, "onStartCommand RUN_1_MIN"); setTemporalTimer(); } else if (extras.containsKey(ALARM_TIMER)) { Log.d(TAG, "onStartCommand ALARM_TIMER"); stopSelf(); } return START_STICKY; } @Override public void onConnected(Bundle bundle) { Log.d(TAG, "locationClient connected"); //Set location update accuracy to whichever value was set last setLocationCheckAccuracy(currentAccuracy); Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); if (mLastLocation != null) { updateLokkiLocation(mLastLocation); } else { Log.e(TAG, "Location is null?! Check location service?!"); // todo add prompt for checking that location services are enabled maybe? } } @Override public void onConnectionSuspended(int i) { } @Override public void onLocationChanged(Location location) { Log.d(TAG, String.format("onLocationChanged - Location: %s", location)); if (serviceRunning && mGoogleApiClient.isConnected() && location != null) { updateLokkiLocation(location); checkBuzzPlaces(); } else { this.stopSelf(); onDestroy(); } } private void updateLokkiLocation(Location location) { if (!MapUtils.useNewLocation(location, lastLocation, INTERVAL_30_SECS)) { Log.d(TAG, "New location discarded."); return; } Log.d(TAG, "New location taken into use."); lastLocation = location; MainApplication.user.setLocationFromAndroidLocation(location); Intent intent = new Intent("LOCATION-UPDATE"); intent.putExtra("current-location", 1); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); if (MainApplication.visible) { try { ServerApi.sendLocation(this, location); } catch (JSONException e) { e.printStackTrace(); } } } private void showArrivalNotification() { Intent showIntent = new Intent(this, BuzzActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, showIntent, 0); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_notify).setContentTitle("GPS Child Location") .setContentText(getString(R.string.you_have_arrived)).setAutoCancel(true) .setContentIntent(contentIntent); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(42, mBuilder.build()); } class VibrationThread implements Runnable { private Place place; VibrationThread(Place place) { this.place = place; } @Override public void run() { Place.Buzz buzz = place.getBuzzObject(); try { while (buzz != null && buzz.getBuzzCount() > 0) { Log.d(TAG, "Vibrating..."); Vibrator v = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(1000); Thread.sleep(2500); buzz.decBuzzCount(); } } catch (InterruptedException e) { e.printStackTrace(); } } } private void triggerBuzzing(Place place) { Place.Buzz buzz = place.getBuzzObject(); if (buzz.isActivated() || buzz.getBuzzCount() <= 0) return; buzz.setActivated(true); Intent i = new Intent(); i.setClass(this, BuzzActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); showArrivalNotification(); Log.d(TAG, "Starting vibration..."); new Thread(new VibrationThread(place)).start(); } private void checkBuzzPlaces() { for (Place place : placeService.getPlacesWithBuzz()) { if (place.getLocation().convertToAndroidLocation().distanceTo(lastLocation) < place.getLocation() .getAcc()) { triggerBuzzing(place); } else { place.setBuzzObject(PlaceService.createBuzz()); } } } @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.e(TAG, "locationClient onConnectionFailed"); } @Override public void onDestroy() { Log.d(TAG, "onDestroy called"); if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); } stopForeground(true); if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); mGoogleApiClient.disconnect(); Log.d(TAG, "Location Updates removed."); } else { Log.e(TAG, "locationClient didn't exist."); } serviceRunning = false; super.onDestroy(); } //------------Service binding------------ /** * The service binder for this service instance */ private final LocationBinder mBinder = new LocationBinder(); /** * Binder object that allows this service to be accessed from other objects in the same process */ public class LocationBinder extends Binder { /** * Gets a reference to the currently running LocationService instance * @return Reference to the current LocationService */ public LocationService getService() { return LocationService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }