Search code examples
androidandroid-pendingintentgoogle-api-clientfusedlocationproviderapi

FusedLocationApi with PendingIntent is fired only once and its null


Am working on an app that needs location update frequently even when its in the background. Following the documentation here, am working with pending intent not locationlistener. My code is below

/**
 * Created by philip on 7/30/16.
 */
public class LocationService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{
    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;

    public static final long UPDATE_INTERVAL_IN_MILLISECONDS =  1000 * 30; //1 minute;
    /**
     * The fastest rate for active location updates. Exact. Updates will never be more frequent
     * than this value.
     */
    public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;

    private String mLastUpdateTime;
    private Location mCurrentLocation;

    private GeoFire mGeoFire = null;
    private String uuid = null;
    private Intent intentService;
    private PendingIntent mPendingIntent;

    @Override
    public void onCreate() {
        super.onCreate();
        Firebase.setAndroidContext(this);
        buildGoogleApiClient();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!mGoogleApiClient.isConnected()) {
            mGoogleApiClient.connect();
        }
        createLocationRequest();
        if(intent != null){
            intentService = new Intent(this, LocationBroadcastReceiver.class);
            intentService.putExtra("UUID", intent.getStringExtra("UUID"));
            intentService.setAction("foo.LOCATION_UPDATE_INTENT");
            mPendingIntent = PendingIntent.getBroadcast(this, 0, intentService, PendingIntent.FLAG_UPDATE_CURRENT);
        }
        return START_REDELIVER_INTENT;
    }

    /********************************************** Google api connection callback below **************************************/

    /***
     * callback fired once connection has been established
     * @param bundle
     */
    @Override
    public void onConnected(Bundle bundle) {
        startLocationUpdates();
        Log.i(this.getClass().getSimpleName(), "Location Service api has been connected");
    }


    /***
     * callback fired when connection fails
     * @param connectionResult
     */
    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.i(this.getClass().getSimpleName(), "Location Service disconnected");
    }


    /**
     * callback fired when connection is temporary suspended
     * @param i
     */
    @Override
    public void onConnectionSuspended(int i) {
        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    /************************************** Ease methods ************************************************/

    /**
     * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
     * LocationServices API.
     */
    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(API)
                .build();
    }

    /**
     * Requests location updates from the FusedLocationApi.
     */
    protected void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
        FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, mPendingIntent);
    }

    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();

        // Sets the desired interval for active location updates. This interval is
        // inexact. You may not receive updates at all if no location sources are available, or
        // you may receive them slower than requested. You may also receive updates faster than
        // requested if other applications are requesting location at a faster interval.
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);

        // Sets the fastest rate for active location updates. This interval is exact, and your
        // application will never receive updates faster than this value.
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);

        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }


}

and my broadcastreceiver

@Override
    public void onReceive(Context context, Intent intent) {
        Firebase.setAndroidContext(context);
        Log.i(getClass().getSimpleName(), "broadcast has been called");

        if (intent != null) {
            String uuid = intent.getStringExtra("UUID");
            mGeoFire = new GeoFire(new Firebase("https://foo.firebaseio.com/users/" + uuid));

            LocationResult locationResult = LocationResult.extractResult(intent);

            if(locationResult == null){// check for null pointer
                Log.i(getClass().getSimpleName(), "location result is null <<<<<<<<<<<<<<<<<<");
                return;
            }

            Log.i(getClass().getSimpleName(), "location result found >>>>>>>>>>>>>>>>>>>>====>>>>>>>>>>>>>>>>>>>");

            location = locationResult.getLastLocation();
        }

        if( isBetterLocation(location, currentBestLocation)){
            currentBestLocation = location;
        }else {
            return;
        }

        if (mGeoFire != null) {
            mGeoFire.setLocation("location", new GeoLocation(location.getLatitude(), location.getLongitude()));
        }
    }

The issue i face is that

  1. My broadcast is only ever called once,
  2. When i do receive the intent, LocationResult locationResult = LocationResult.extractResult(intent); returns null

and that is the last i hear from my broadcast reveiver


Solution

  • I basically moved my listener to onConnected and checked for null. It worked fine after then.

    package com.github.robophil.location_service;
    
    import android.Manifest;
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.content.pm.PackageManager;
    import android.location.Location;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.support.v4.app.ActivityCompat;
    import android.util.Log;
    import android.widget.Toast;
    
    import com.firebase.client.Firebase;
    import com.firebase.geofire.GeoFire;
    import com.google.android.gms.common.ConnectionResult;
    import com.google.android.gms.common.api.GoogleApiClient;
    import com.google.android.gms.location.LocationRequest;
    import com.google.android.gms.location.LocationResult;
    
    import static com.google.android.gms.location.LocationServices.API;
    import static com.google.android.gms.location.LocationServices.FusedLocationApi;
    
    /**
     * Created by philip on 7/30/16.
     */
    public class LocationService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
        private GoogleApiClient mGoogleApiClient;
        private LocationRequest mLocationRequest;
    
        public static final String prefName = "com.github.robophil.pref";
    
        public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 1000 * 60; //1 minute;
        /**
         * The fastest rate for active location updates. Exact. Updates will never be more frequent
         * than this value.
         */
        public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
                UPDATE_INTERVAL_IN_MILLISECONDS / 2;
    
        private String mLastUpdateTime;
        private Location mCurrentLocation;
    
        private GeoFire mGeoFire = null;
        private String uuid = null;
        private Intent intentService;
        private PendingIntent mPendingIntent;
    
        @Override
        public void onCreate() {
            super.onCreate();
            Firebase.setAndroidContext(this);
            buildGoogleApiClient();
        }
    
        @Override
        public void onDestroy() {
            if(mGoogleApiClient != null){
                if(mGoogleApiClient.isConnected()){
                    mGoogleApiClient.disconnect();
                    FusedLocationApi.removeLocationUpdates(mGoogleApiClient, mPendingIntent);
                }
            }
            super.onDestroy();
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (intent != null) {
                if(!intent.hasExtra("UUID") || !intent.hasExtra("URL")){
                    Log.i(getClass().getSimpleName(), "Service has stopped itself");
                    Toast.makeText(this, "stopself called", Toast.LENGTH_SHORT).show();
                    stopSelf();
                }
    
                SharedPreferences.Editor editor = getSharedPreferences(LocationService.prefName, getApplicationContext().MODE_PRIVATE).edit();
                editor.putString("UUID", intent.getStringExtra("UUID"));
                editor.putString("URL", intent.getStringExtra("URL"));
                editor.apply();
    
                intentService = new Intent(this, LocationIntentService.class);
                intentService.setExtrasClassLoader(LocationResult.class.getClassLoader());
                mPendingIntent = PendingIntent.getService(getApplicationContext(), 0, intentService, PendingIntent.FLAG_UPDATE_CURRENT);
            }
    
            if (!mGoogleApiClient.isConnected()) {
                mGoogleApiClient.connect();
            }
    
            return START_REDELIVER_INTENT;
        }
    
        /********************************************** Google api connection callback below **************************************/
    
        /***
         * callback fired once connection has been established
         * @param bundle
         */
        @Override
        public void onConnected(Bundle bundle) {
            createLocationRequest();
            startLocationUpdates();
            Log.i(this.getClass().getSimpleName(), "Location Service api has been connected");
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                return;
            }
            Location location = FusedLocationApi.getLastLocation(mGoogleApiClient);
            if (location == null){
                Log.i(getClass().getSimpleName(), "init location is null");
                return;
            }
            Log.i(getClass().getSimpleName(), "init location ==> "+location.getAccuracy()+" "+location.getProvider());
        }
    
    
        /***
         * callback fired when connection fails
         * @param connectionResult
         */
        @Override
        public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
            Log.i(this.getClass().getSimpleName(), "Location Service disconnected");
        }
    
    
        /**
         * callback fired when connection is temporary suspended
         * @param i
         */
        @Override
        public void onConnectionSuspended(int i) {
            if (mGoogleApiClient != null) {
                mGoogleApiClient.connect();
            }
        }
    
        /************************************** Ease methods ************************************************/
    
        /**
         * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
         * LocationServices API.
         */
        protected synchronized void buildGoogleApiClient() {
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(API)
                    .build();
        }
    
        /**
         * Requests location updates from the FusedLocationApi.
         */
        protected void startLocationUpdates() {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                return;
            }
    //        FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
            FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, mPendingIntent);
        }
    
        protected void createLocationRequest() {
            mLocationRequest = new LocationRequest();
    
            // Sets the desired interval for active location updates. This interval is
            // inexact. You may not receive updates at all if no location sources are available, or
            // you may receive them slower than requested. You may also receive updates faster than
            // requested if other applications are requesting location at a faster interval.
            mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
    
            // Sets the fastest rate for active location updates. This interval is exact, and your
            // application will never receive updates faster than this value.
            mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
    
            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        }
    
    
    }
    

    LocationsIntentService used for my pending intent stays the same, just checking for null before doing anything

    package com.github.robophil.location_service;
    
    import android.app.IntentService;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.location.Location;
    import android.os.Bundle;
    import android.util.Log;
    
    import com.firebase.client.Firebase;
    import com.firebase.geofire.GeoFire;
    import com.firebase.geofire.GeoLocation;
    import com.google.android.gms.location.LocationResult;
    
    import java.util.Set;
    
    /**
     * Created by philip on 8/7/16.
     */
    public class LocationIntentService extends IntentService {
        private GeoFire mGeoFire;
        private Location location, currentBestLocation;
    
        /**
         * Creates an IntentService.  Invoked by your subclass's constructor.
         *
         * @param name Used to name the worker thread, important only for debugging.
         */
        public LocationIntentService(String name) {
            super("i am a value");
        }
    
        public LocationIntentService(){
            super("i am a value");
        }
    
    
    
        @Override
        protected void onHandleIntent(Intent intent) {
            Firebase.setAndroidContext(this);
            Log.i(getClass().getSimpleName(), "intent service has been called");
    
            SharedPreferences pref = getSharedPreferences(LocationService.prefName, getApplicationContext().MODE_PRIVATE);
            String uuid = pref.getString("UUID", null);
            String url = pref.getString("URL", null);
    
            if(uuid == null || url==null){
                stopSelf();
                return;
            }
    
            if (intent != null) {
    
                mGeoFire = new GeoFire(new Firebase(url +"/"+ uuid));
                Log.i(getClass().getSimpleName(), "user id found ==> "+uuid+", "+url);
    
                Bundle extra = intent.getExtras();
                Set<String> extraKeySet = extra.keySet();
                for(String key: extraKeySet){
                    Log.i(getClass().getSimpleName(), "key found ==> "+ key);
                }
    
                if(LocationResult.hasResult(intent)){
                    Log.i(getClass().getSimpleName(), "intent contains location");
                }
    
                LocationResult locationResult = LocationResult.extractResult(intent);
    
                if(locationResult == null){// check for null pointer
                    Log.i(getClass().getSimpleName(), "location result is null ...");
                    return;
                }
    
                Log.i(getClass().getSimpleName(), "location result found");
    
                location = locationResult.getLastLocation();
            }
    
            if( isBetterLocation(location, currentBestLocation)){
                currentBestLocation = location;
            }else {
                return;
            }
    
            if (mGeoFire != null) {
                mGeoFire.setLocation("location", new GeoLocation(location.getLatitude(), location.getLongitude()));
            }
        }
    
        private static final int TWO_MINUTES = 1000 * 60 * 2;
    
        /** Determines whether one Location reading is better than the current Location fix
         * @param location  The new Location that you want to evaluate
         * @param currentBestLocation  The current Location fix, to which you want to compare the new one
         */
        protected boolean isBetterLocation(Location location, Location currentBestLocation) {
            if (currentBestLocation == null) {
                // A new location is always better than no location
                return true;
            }
    
            // Check whether the new location fix is newer or older
            long timeDelta = location.getTime() - currentBestLocation.getTime();
            boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
            boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
            boolean isNewer = timeDelta > 0;
    
            // If it's been more than two minutes since the current location, use the new location
            // because the user has likely moved
            if (isSignificantlyNewer) {
                return true;
                // If the new location is more than two minutes older, it must be worse
            } else if (isSignificantlyOlder) {
                return false;
            }
    
            // Check whether the new location fix is more or less accurate
            int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
            boolean isLessAccurate = accuracyDelta > 0;
            boolean isMoreAccurate = accuracyDelta < 0;
            boolean isSignificantlyLessAccurate = accuracyDelta > 200;
    
            // Check if the old and new location are from the same provider
            boolean isFromSameProvider = isSameProvider(location.getProvider(),
                    currentBestLocation.getProvider());
    
            // Determine location quality using a combination of timeliness and accuracy
            if (isMoreAccurate) {
                return true;
            } else if (isNewer && !isLessAccurate) {
                return true;
            } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
                return true;
            }
            return false;
        }
    
        /** Checks whether two providers are the same */
        private boolean isSameProvider(String provider1, String provider2) {
            if (provider1 == null) {
                return provider2 == null;
            }
            return provider1.equals(provider2);
        }
    }