LocationClient doesn't give callback when screen light goes off but my WakefulThread is running flawlessly as expected
Asked Answered
T

2

7

To retrieve fused location in background, I have created a library which is very similar to cwac-locpoll library created by Commonsguy.

Inside PollerThread , I am trying to connect, request and retrieve the locations using LocationClient.

I am able to get connected by receiving callback on onConnected method but I am not able to get callback on onLocationChanged method.so my onTimeout thread executes as per decided interval.

NOTE: This issue happens only when screen light goes off.otherwise it works completely fine.

I suspect there might be bug in new Location Api.

Here is the implementation of my PollerThread,

          private class PollerThread extends WakefulThread  implements GooglePlayServicesClient.ConnectionCallbacks,
      GooglePlayServicesClient.OnConnectionFailedListener,LocationListener{

            private static final String TAG = "PollerThread";

            //context
            private Context mContext=null;
            private LocationClient mLocationClient=null;
            private LocationRequest mLocationRequest=null;
            private LocationManager locMgr=null;
            private Intent intentTemplate=null;
            private Handler handler=new Handler();
            private Runnable onTimeout = new Runnable() {

            @Override
            public void run() {

            Log.e(TAG, "onTimeout");

            //prepare broadcast intent
              Intent toBroadcast=new Intent(intentTemplate);
              toBroadcast.putExtra(FusedPoller.EXTRA_ERROR, "Timeout!");
              toBroadcast.putExtra(
                      FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, false);
              toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
                      mLocationClient.getLastLocation());
              sendBroadcast(toBroadcast);

            //stop the thread
              quit();
            }
          };

        PollerThread(Context mContext,LocationRequest mLocationRequest,PowerManager.WakeLock lock, LocationManager locMgr,
                     Intent intentTemplate) {
          super(lock, "LocationPoller-PollerThread");

          Log.e(TAG, "PollerThread");

          this.mContext=mContext;
          this.mLocationRequest=mLocationRequest;
          this.locMgr=locMgr; 
          this.intentTemplate=intentTemplate;
        }


        @Override
        protected void onPreExecute() {
            super.onPreExecute();
              Log.e(TAG, "onPreExecute");

            //setup timeout
            setTimeoutAlarm();

            //initiate connection
            initiateConnection();
        }

        @Override
        protected void onPostExecute() {
          super.onPostExecute();
          Log.e(TAG, "onPostExecute");

          //remove timeout
          removeTimeoutAlarm();
          //disconnect
          initiateDisconnection();
        }

        /**
         * Called when the WakeLock is completely unlocked.
         * Stops the service, so everything shuts down.
         */
        @Override
        protected void onUnlocked() {
            Log.e(TAG, "onUnlocked");
            stopSelf();
        }


        private void setTimeoutAlarm() {
            Log.e(TAG, "setTimeoutAlarm");
            handler.postDelayed(onTimeout, FusedLocationUtils.DEFAULT_TIMEOUT);
        }

        private void removeTimeoutAlarm()
        {
            Log.e(TAG, "removeTimeoutAlarm");
            handler.removeCallbacks(onTimeout);
        }

        private void initiateConnection()
        {
            Log.e(TAG, "initiateConnection");
            mLocationClient = new LocationClient(this.mContext, this, this);
            mLocationClient.connect();
        }

        private void initiateDisconnection()
        {
            Log.e(TAG, "initiateDisconnection");
            if(mLocationClient.isConnected())
            {
                mLocationClient.disconnect();
            }
        }


        @Override
        public void onConnected(Bundle arg0) {
            Log.e(TAG, "onConnected");


            Log.e(TAG, "provider: GPS-"+locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)+" NETWORK-"+locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER));

                if (!(locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)) && !(locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
                    Log.e(TAG, "both disabled");

                    //get last location and broadcast it
                    getLastLocationAndBroadcast();

                    //stop the thread
                    quit();
                }
                else
                {
                    Log.e(TAG, "provider enabled");

                    //get latest location and broadcast it
                    getLatestLocationAndBroadcast();
                    //don't quit from here,quit from onLocationChanged
                }

        }


        @Override
        public void onDisconnected() {
            Log.e(TAG, "onDisconnected");
            // TODO Auto-generated method stub

        }

        @Override
        public void onConnectionFailed(ConnectionResult arg0) {
            Log.e(TAG, "onConnectionFailed");
            // TODO Auto-generated method stub

        }
        @Override
        public void onLocationChanged(Location location) {


            Log.e(TAG, "onLocationChanged");

            //prepare broadcast intent
            Intent toBroadcast=new Intent(intentTemplate);
            toBroadcast.putExtra(FusedPoller.EXTRA_LOCATION, location);
            sendBroadcast(toBroadcast);

            //stop further updates
            stopUpdates();
            //stop the thread
            quit();

        }

        private void getLatestLocationAndBroadcast() {
            Log.e(TAG, "getLatestLocationAndBroadcast");
            if(mLocationClient.isConnected() && servicesConnected())
            {
                Log.e(TAG, "going to request updates");
                Log.e(TAG, "lockStatic.isHeld(): "+lockStatic.isHeld());
                mLocationClient.requestLocationUpdates(mLocationRequest, this);
            }
            else
            {
                Log.e(TAG, "not going to request updates");
            }
        }


        private void stopUpdates() {
            Log.e(TAG, "stopUpdates");
            if(servicesConnected())
            {
                Log.e(TAG,getString(R.string.location_updates_stopped));
                mLocationClient.removeLocationUpdates(this);
            }
            else
            {
                Log.e(TAG,"can't do:"+getString(R.string.location_updates_stopped));
            }
        }



        private void getLastLocationAndBroadcast() {
            Log.e(TAG, "getLastLocationAndBroadcast");
            if(mLocationClient.isConnected() && servicesConnected())
            {
                Log.e(TAG, "going to get last location: "+mLocationClient.getLastLocation());

                Intent toBroadcast = new Intent(intentTemplate);
                toBroadcast.putExtra(FusedPoller.EXTRA_ERROR,
                        "Location Provider disabled!");
                toBroadcast.putExtra(
                        FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, true);
                toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
                        mLocationClient.getLastLocation());
                sendBroadcast(toBroadcast);
            }
            else
            {
                Log.e(TAG, "not going to get last location");
            }
        }
      }

and servicesConnected method implementation,

            /**
         * Verify that Google Play services is available before making a request.
         *
         * @return true if Google Play services is available, otherwise false
         */
        private boolean servicesConnected() {

              Log.e(TAG, "servicesConnected");

            // Check that Google Play services is available
            int resultCode =
                    GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

            // If Google Play services is available
            if (ConnectionResult.SUCCESS == resultCode) {
                // In debug mode, log the status
                Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_available));

                // Continue
                return true;
            // Google Play services was not available for some reason
            } else {
                // Display an error dialog
                Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_unavailable));
                Toast.makeText(this, getString(R.string.play_services_unavailable), Toast.LENGTH_SHORT).show();

                return false;
            }
        }
Tammara answered 12/7, 2013 at 11:25 Comment(5)
Are you sure that you are holding a WakeLock?Cleaves
@CommonsWare: Yes,I am sure as I have printed log inside getLatestLocationAndBroadcast method to know the status of lockStatic.isHeld() .and it is giving positive response.Tammara
Not sure what to tell you, sorry.Cleaves
@CommonsWare: It's ok sir.but can you guide me about where should I post this issue to get it resolved?is there any support team from google regarding new Location API?Tammara
@CommonsWare: anyone has news? may be, the problem, related to new android versions? Is it a bug? workarounds?Ranson
K
20

If you want to listen to frequent location updates in the background (e.g., every second), you should be running your code inside a Service:

http://developer.android.com/reference/android/app/Service.html

Activities can be ended by the Android platform at any point in time in which they are not in the foreground.

When using a Service, I would recommend having the Service implement the LocationListener directly, and not a Thread inside the Service. For example, use:

public class LocListener extends Service implements com.google.android.gms.location.LocationListener, ...{

I've used this design of implementing the LocationListener directly on the Service with the LocationClient and fused location provider in my GPS Benchmark app and I can confirm that this works even when the screen is off and the app is running in the background.

If you want to listen to occasional location updates in the background (e.g., every minute) using the fused location provider, a better design is to use PendingIntents, using the LocationClient.requestLocationUpdates(Location Request, PendingIntent callbackIntent) method:

https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20android.app.PendingIntent)

From the above Android doc:

This method is suited for the background use cases, more specifically for receiving location updates, even when the app has been killed by the system. In order to do so, use a PendingIntent for a started service. For foreground use cases, the LocationListener version of the method is recommended, see requestLocationUpdates(LocationRequest, LocationListener).

Any previous LocationRequests registered on this PendingIntent will be replaced.

Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value on the intent.

See the Activity Recognition example for a more detailed description of using PendingIntents to get updates while running in the background:

https://developer.android.com/training/location/activity-recognition.html

Modified excerpts from this documentation are below, changed by me to be specific to location updates.

First declare the Intent:

public class MainActivity extends FragmentActivity implements
        ConnectionCallbacks, OnConnectionFailedListener {
    ...
    ...
    /*
     * Store the PendingIntent used to send location updates
     * back to the app
     */
    private PendingIntent mLocationPendingIntent;
    // Store the current location client
    private LocationClient mLocationClient;
    ...
}

Request updates as you currently are, but this time pass in the pending intent:

/*
 * Create the PendingIntent that Location Services uses
 * to send location updates back to this app.
 */
Intent intent = new Intent(
        mContext, LocationIntentService.class);

...
//Set up LocationRequest with desired parameter here
...
/*
 * Request a PendingIntent that starts the IntentService.
 */
mLocationPendingIntent =
        PendingIntent.getService(mContext, 0, intent,
        PendingIntent.FLAG_UPDATE_CURRENT);
/*
 * Request location updates
 */
mLocationClient.requestLocationUpdates(mLocationRequest, callbackIntent);

Handle Location Updates

To handle the Intent that Location Services sends for each update interval, define an IntentService and its required method onHandleIntent(). Location Services sends out ... updates as Intent objects, using the the PendingIntent you provided when you called requestLocationUpdates(). Since you provided an explicit intent for the PendingIntent, the only component that receives the intent is the IntentService you're defining.

Define the class and the required method onHandleIntent():

/**
 * Service that receives Location updates. It receives
 * updates in the background, even if the main Activity is not visible.
 */
public class LocationIntentService extends IntentService {
    ...
    /**
     * Called when a new location update is available.
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle b = intent.getExtras();
        Location loc = (Location) b.get(LocationClient.KEY_LOCATION_CHANGED);
        Log.d(TAG, "Updated location: " + loc.toString());


    }
    ...
}

IMPORTANT - to be as efficient as possible, your code in onHandleIntent() should return as quickly as possible to allow the IntentService to shut down. From IntentService docs:

http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)

This method is invoked on the worker thread with a request to process. Only one Intent is processed at a time, but the processing happens on a worker thread that runs independently from other application logic. So, if this code takes a long time, it will hold up other requests to the same IntentService, but it will not hold up anything else. When all requests have been handled, the IntentService stops itself, so you should not call stopSelf().

My understanding of the IntentService design is that you can spawn Threads inside onHandleIntent() to avoid blocking other location updates via platform calls to onHandleIntent(), just be aware that the Service will continue to run until all the running threads terminate.

Kannan answered 25/7, 2013 at 14:42 Comment(14)
I have used Service and I am using LocationListener also.you can have a look at this implementation https://github.com/commonsguy/cwac-locpoll/blob/master/src/com/commonsware/cwac/locpoll/LocationPollerService.java ,As posted in question I am trying to build similar library and I have implemented PollerThread differently according to my requirement because of new Location API.Tammara
The LocationClient was implemented with the goal of moving developers away from managing their own wakelocks and services in the background, to make location tracking more efficient. The idea of the fused location provider is for the application to specify a general balance for accuracy/energy, and then let the fused provider take care of low-level process management to obtain the samples. The app should only need to listen for updates. Unless the Android team says otherwise, this "bug" may be by design, or at least not an area they've heavily tested, expecting devs to use PendingIntents.Kannan
I am agreed with the idea of fused location provider but in case of tracking the locations in background mode, devs have to write service to get frequent updates and that service should hold the wakelock to keep the CPU awake.so ultimately devs have to manage these things at their own.and that is what I am doing.In short,It is not acceptable behavior that new Fused Location API can provide frequent location updates only in foreground mode.Tammara
What else are you doing in the Service that requires it to always be active in the background? And what is location update rate (i.e. minTime) value are you using?Kannan
Try implementing LocationListener directly on Service class, instead of in separate thread. I've added more info to my original response on this, as its worked for me in my app.Kannan
The code of Service is almost similar to library of CommonsWare.and location update rate is 1 minute.I will try to implement LocationListener directly on Service and I will let you know.Tammara
Thanks for the guidance.Implementing LocationListener directly to Service almost solved the issue.I am getting callback for 80% of my requests.so I am planning to use PendingIntent instead of LocationListener.and Instead of invoking IntentService,should I invoke BroadcastReceiver ? Is it fine to do network communication inside BroadcastReceiver ?Tammara
Actually, after reading the Android docs again, I needed to revise my characterization of long-running tasks in IntentService.onHandleIntent(). See bottom part of answer for new summary. I'd use IntentService instead of BroadcastReceiver.Kannan
Thanks a lot for the detailed guidance.It is safe to implement using IntentService instead of BroadcastReceiver as per my requirement.and I have gone through your profile,you are holding PhD in LocationBasedServices.If you have written any blog or concepts regarding it,I would like to read and improve my knowledge.Tammara
bit.ly/IEEE_Per_Com_LAISYC summarizes my dissertation work in mobile app optimization for LBS. Best Android resource for new fused provider is 2013 I/O session goo.gl/2q2nQ.Kannan
yes,I have seen all I/O sessions.and thanks a lot for sharing summary of dissertation,it is really very useful.Tammara
Glad you found it useful! :)Kannan
@Sean Barbeau , i found that when my screen is locked, my locationclient stops giving me updates, even if my service continues to run. Help!Ranson
ActivityRecognition events don't pass if the screen is locked. At least not on all devices.Jetblack
G
0

I've spent days trying to get WiFi and cell-based locations with locked screen with Android 6.0 on Nexus 6. And looks like the native android location service simple does not allow to do it. Once device got locked it still collects location update events for 10-15 minutes then stops to providing any of location updates.

In my case the solution was to switch from native Android location service to Google Play Services wrapper called com.google.android.gms.location: https://developers.google.com/android/reference/com/google/android/gms/location/package-summary

Yes, I know that some of Android devices lack of GMS, but for my application this is the only solution to perform.

It does not stop sending location updates even when in the background and device screen is locked.

Me personally prefer RxJava library to wrap this service into a stream (examples included): https://github.com/mcharmas/Android-ReactiveLocation

Godwin answered 23/8, 2016 at 10:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.