/BackgroundLocationService.java Secret
-
Star
(148)
You must be signed in to star a gist -
Fork
(49)
You must be signed in to fork a gist
-
-
Save blackcj/20efe2ac885c7297a676 to your computer and use it in GitHub Desktop.
| /** | |
| * | |
| * BackgroundLocationService used for tracking user location in the background. | |
| * @author cblack | |
| */ | |
| public class BackgroundLocationService extends Service implements | |
| GoogleApiClient.ConnectionCallbacks, | |
| GoogleApiClient.OnConnectionFailedListener, | |
| LocationListener { | |
| IBinder mBinder = new LocalBinder(); | |
| private GoogleApiClient mGoogleApiClient; | |
| private PowerManager.WakeLock mWakeLock; | |
| private LocationRequest mLocationRequest; | |
| // Flag that indicates if a request is underway. | |
| private boolean mInProgress; | |
| private Boolean servicesAvailable = false; | |
| public class LocalBinder extends Binder { | |
| public BackgroundLocationService getServerInstance() { | |
| return BackgroundLocationService.this; | |
| } | |
| } | |
| @Override | |
| public void onCreate() { | |
| super.onCreate(); | |
| mInProgress = false; | |
| // Create the LocationRequest object | |
| mLocationRequest = LocationRequest.create(); | |
| // Use high accuracy | |
| mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); | |
| // Set the update interval to 5 seconds | |
| mLocationRequest.setInterval(Constants.UPDATE_INTERVAL); | |
| // Set the fastest update interval to 1 second | |
| mLocationRequest.setFastestInterval(Constants.FASTEST_INTERVAL); | |
| servicesAvailable = servicesConnected(); | |
| /* | |
| * Create a new location client, using the enclosing class to | |
| * handle callbacks. | |
| */ | |
| setUpLocationClientIfNeeded(); | |
| } | |
| /* | |
| * Create a new location client, using the enclosing class to | |
| * handle callbacks. | |
| */ | |
| protected synchronized void buildGoogleApiClient() { | |
| this.mGoogleApiClient = new GoogleApiClient.Builder(this) | |
| .addConnectionCallbacks(this) | |
| .addOnConnectionFailedListener(this) | |
| .addApi(LocationServices.API) | |
| .build(); | |
| } | |
| private boolean servicesConnected() { | |
| // Check that Google Play services is available | |
| int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); | |
| // If Google Play services is available | |
| if (ConnectionResult.SUCCESS == resultCode) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| public int onStartCommand (Intent intent, int flags, int startId) | |
| { | |
| super.onStartCommand(intent, flags, startId); | |
| PowerManager mgr = (PowerManager)getSystemService(Context.POWER_SERVICE); | |
| /* | |
| WakeLock is reference counted so we don't want to create multiple WakeLocks. So do a check before initializing and acquiring. | |
| This will fix the "java.lang.Exception: WakeLock finalized while still held: MyWakeLock" error that you may find. | |
| */ | |
| if (this.mWakeLock == null) { //**Added this | |
| this.mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock"); | |
| } | |
| if (!this.mWakeLock.isHeld()) { //**Added this | |
| this.mWakeLock.acquire(); | |
| } | |
| if(!servicesAvailable || mGoogleApiClient.isConnected() || mInProgress) | |
| return START_STICKY; | |
| setUpLocationClientIfNeeded(); | |
| if(!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) | |
| { | |
| appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Started", Constants.LOG_FILE); | |
| mInProgress = true; | |
| mGoogleApiClient.connect(); | |
| } | |
| return START_STICKY; | |
| } | |
| private void setUpLocationClientIfNeeded() | |
| { | |
| if(mGoogleApiClient == null) | |
| buildGoogleApiClient(); | |
| } | |
| // Define the callback method that receives location updates | |
| @Override | |
| public void onLocationChanged(Location location) { | |
| // Report to the UI that the location was updated | |
| String msg = Double.toString(location.getLatitude()) + "," + | |
| Double.toString(location.getLongitude()); | |
| Log.d("debug", msg); | |
| // Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); | |
| appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ":" + msg, Constants.LOCATION_FILE); | |
| } | |
| @Override | |
| public IBinder onBind(Intent intent) { | |
| return mBinder; | |
| } | |
| public String getTime() { | |
| SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |
| return mDateFormat.format(new Date()); | |
| } | |
| public void appendLog(String text, String filename) | |
| { | |
| File logFile = new File(filename); | |
| if (!logFile.exists()) | |
| { | |
| try | |
| { | |
| logFile.createNewFile(); | |
| } | |
| catch (IOException e) | |
| { | |
| // TODO Auto-generated catch block | |
| e.printStackTrace(); | |
| } | |
| } | |
| try | |
| { | |
| //BufferedWriter for performance, true to set append to file flag | |
| BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); | |
| buf.append(text); | |
| buf.newLine(); | |
| buf.close(); | |
| } | |
| catch (IOException e) | |
| { | |
| // TODO Auto-generated catch block | |
| e.printStackTrace(); | |
| } | |
| } | |
| @Override | |
| public void onDestroy() { | |
| // Turn off the request flag | |
| this.mInProgress = false; | |
| if (this.servicesAvailable && this.mGoogleApiClient != null) { | |
| this.mGoogleApiClient.unregisterConnectionCallbacks(this); | |
| this.mGoogleApiClient.unregisterConnectionFailedListener(this); | |
| this.mGoogleApiClient.disconnect(); | |
| // Destroy the current location client | |
| this.mGoogleApiClient = null; | |
| } | |
| // Display the connection status | |
| // Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ": | |
| // Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show(); | |
| if (this.mWakeLock != null) { | |
| this.mWakeLock.release(); | |
| this.mWakeLock = null; | |
| } | |
| super.onDestroy(); | |
| } | |
| /* | |
| * Called by Location Services when the request to connect the | |
| * client finishes successfully. At this point, you can | |
| * request the current location or start periodic updates | |
| */ | |
| @Override | |
| public void onConnected(Bundle bundle) { | |
| // Request location updates using static settings | |
| Intent intent = new Intent(this, LocationReceiver.class); | |
| LocationServices.FusedLocationApi.requestLocationUpdates(this.mGoogleApiClient, | |
| mLocationRequest, this); // This is the changed line. | |
| appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Connected", Constants.LOG_FILE); | |
| } | |
| /* | |
| * Called by Location Services if the connection to the | |
| * location client drops because of an error. | |
| */ | |
| @Override | |
| public void onConnectionSuspended(int i) { | |
| // Turn off the request flag | |
| mInProgress = false; | |
| // Destroy the current location client | |
| mGoogleApiClient = null; | |
| // Display the connection status | |
| // Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ": Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show(); | |
| appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Disconnected", Constants.LOG_FILE); | |
| } | |
| /* | |
| * Called by Location Services if the attempt to | |
| * Location Services fails. | |
| */ | |
| @Override | |
| public void onConnectionFailed(ConnectionResult connectionResult) { | |
| mInProgress = false; | |
| /* | |
| * Google Play services can resolve some errors it detects. | |
| * If the error has a resolution, try sending an Intent to | |
| * start a Google Play services activity that can resolve | |
| * error. | |
| */ | |
| if (connectionResult.hasResolution()) { | |
| // If no resolution is available, display an error dialog | |
| } else { | |
| } | |
| } | |
| } |
| apply plugin: 'com.android.application' | |
| android { | |
| compileSdkVersion 22 | |
| buildToolsVersion "22.0.1" | |
| defaultConfig { | |
| applicationId "com.blackcj.locationtracker" | |
| minSdkVersion 18 | |
| targetSdkVersion 22 | |
| versionCode 1 | |
| versionName "1.0" | |
| } | |
| buildTypes { | |
| release { | |
| minifyEnabled false | |
| proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |
| } | |
| } | |
| } | |
| dependencies { | |
| compile fileTree(dir: 'libs', include: ['*.jar']) | |
| testCompile 'junit:junit:4.12' | |
| compile 'com.android.support:appcompat-v7:22.2.1' | |
| compile 'com.google.android.gms:play-services-location:8.4.0' | |
| } |
| public final class Constants { | |
| // Milliseconds per second | |
| private static final int MILLISECONDS_PER_SECOND = 1000; | |
| // Update frequency in seconds | |
| private static final int UPDATE_INTERVAL_IN_SECONDS = 60; | |
| // Update frequency in milliseconds | |
| public static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS; | |
| // The fastest update frequency, in seconds | |
| private static final int FASTEST_INTERVAL_IN_SECONDS = 60; | |
| // A fast frequency ceiling in milliseconds | |
| public static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS; | |
| // Stores the lat / long pairs in a text file | |
| public static final String LOCATION_FILE = "sdcard/location.txt"; | |
| // Stores the connect / disconnect data in a text file | |
| public static final String LOG_FILE = "sdcard/log.txt"; | |
| public static final String RUNNING = "runningInBackground"; // Recording data in background | |
| public static final String APP_PACKAGE_NAME = "com.blackcj.locationtracker"; | |
| /** | |
| * Suppress default constructor for noninstantiability | |
| */ | |
| private Constants() { | |
| throw new AssertionError(); | |
| } | |
| } |
| public class LocationLoggerServiceManager extends BroadcastReceiver { | |
| private SharedPreferences mPrefs; | |
| public static final String TAG = "LLoggerServiceManager"; | |
| @Override | |
| public void onReceive(Context context, Intent intent) { | |
| // Make sure we are getting the right intent | |
| if( "android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) { | |
| boolean mUpdatesRequested = false; | |
| // Open the shared preferences | |
| mPrefs = context.getSharedPreferences( | |
| Constants.APP_PACKAGE_NAME, Context.MODE_PRIVATE); | |
| /* | |
| * Get any previous setting for location updates | |
| * Gets "false" if an error occurs | |
| */ | |
| if (mPrefs.contains(Constants.RUNNING)) { | |
| mUpdatesRequested = mPrefs.getBoolean(Constants.RUNNING, false); | |
| } | |
| if(mUpdatesRequested){ | |
| ComponentName comp = new ComponentName(context.getPackageName(), BackgroundLocationService.class.getName()); | |
| ComponentName service = context.startService(new Intent().setComponent(comp)); | |
| if (null == service){ | |
| // something really wrong here | |
| Log.e(TAG, "Could not start service " + comp.toString()); | |
| } | |
| } | |
| } else { | |
| Log.e(TAG, "Received unexpected intent " + intent.toString()); | |
| } | |
| } | |
| } |
| <?xml version="1.0" encoding="utf-8"?> | |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
| package="com.blackcj.locationtracker"> | |
| <!-- Used to start logging after re-boot --> | |
| <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | |
| <uses-permission android:name="android.permission.INTERNET" /> | |
| <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |
| <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> | |
| <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |
| <uses-permission android:name="android.permission.WAKE_LOCK" /> | |
| <application | |
| android:allowBackup="true" | |
| android:icon="@mipmap/ic_launcher" | |
| android:label="@string/app_name" | |
| android:supportsRtl="true" | |
| android:theme="@style/AppTheme"> | |
| <activity android:name=".MainActivity"> | |
| <intent-filter> | |
| <action android:name="android.intent.action.MAIN" /> | |
| <category android:name="android.intent.category.LAUNCHER" /> | |
| </intent-filter> | |
| </activity> | |
| <receiver android:name=".LocationLoggerServiceManager" | |
| android:enabled="true" | |
| android:exported="false" | |
| android:label="LocationLoggerServiceManager"> | |
| <intent-filter> | |
| <action android:name="android.intent.action.BOOT_COMPLETED" /> | |
| </intent-filter> | |
| </receiver> | |
| <service android:name=".BackgroundLocationService" | |
| android:enabled="true" | |
| android:exported="true" > | |
| <intent-filter> | |
| <action | |
| android:name="com.google.android.gms.wearable.BIND_LISTENER" /> | |
| </intent-filter> | |
| </service> | |
| </application> | |
| </manifest> |
Can't this all just be done using a PendingIntent?
Hello, in the onConnectionFailed there is a comments that says:
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/The problem is that in the Service i cannot solve this, i have this code in an activity right now:
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
if (mResolvingError) { // Already attempting to resolve an error.
return;
} else if (connectionResult.hasResolution()) {
try {
mResolvingError = true;
connectionResult.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) { // There was an error with the resolution intent. Try again.
mGoogleApiClient.connect();
}
} else { // Show dialog using GoogleApiAvailability.getErrorDialog()
showErrorDialog(connectionResult.getErrorCode());
mResolvingError = true;
}
}As you can see i need the onActivityResult method and other stuff from activity, if this callback is called in the service how can i launch an intent to solve this? If the GoogleApiClient object is on the service how can i can communicate that the problem was solved?
I'm having a bad time trying to implement this because this Service is exactly what i was needing and it works fine but what happen if the onConnectionFailed is called? I need to know the position of the client every few minutes.
Hi newbie here. How does the service communicate back to the calling activity with the completed work?
Hi All,
I found this Gist very useful so I just wanted to thank you all for sharing.
also @andrewclam1991 I communicate back to the activity doing the following changes:
- on the BackgroundLocationService make it implement LocationListener which will make you override "onLocationChanged(Location location)" function
- from that function "onLocationChanged" you can send a broadcast like this: "sendBroadcast(Intent(ACTION_SEND_UI_UPDATE))"
- On your activity you can register for that event like this (code is in Kotlin but you get the idea):
override fun onStart() { super.onStart() registerReceiver(mUpdateUIListener, BackgroundLocationService.intentFilter) } override fun onStop() { super.onStop() unregisterReceiver(mUpdateUIListener) } private val mUpdateUIListener = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { ACTION_SEND_UI_UPDATE -> { //Do some work here } } } }
Hope this is useful for someone
Use this
GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) instead of this GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
Hello there, how come when i implemented this, background service when i use stopService(i) // i = intent
Hi, I was able to resolve my issue.... I did the following:
Thanks for your help.