-
-
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.