简体   繁体   中英

App crashes on disabling location permission from settings

I have a service class that continuously runs in background to fetch location updates. Everything works as expected but when I disable the permission from the settings, the app crashes. I couldn't figure out why the app is crashing when I disable the location permission from the settings. Below is what I get in logcat.

FATAL EXCEPTION: GoogleApiHandler
                 Process: com.android.myapp, PID: 7703
                 java.lang.SecurityException: Client must have ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to perform any location operations.
                 at android.os.Parcel.readException(Parcel.java:1683)
                 at android.os.Parcel.readException(Parcel.java:1636)
                 at com.google.android.gms.internal.zzeu.zza(Unknown Source)
                 at com.google.android.gms.internal.zzcfa.zzif(Unknown Source)
                 at com.google.android.gms.internal.zzcfd.getLastLocation(Unknown Source)
                 at com.google.android.gms.internal.zzcfk.getLastLocation(Unknown Source)
                 at com.google.android.gms.location.zzg.zza(Unknown Source)
                 at com.google.android.gms.common.api.internal.zze.zza(Unknown Source)
                 at com.google.android.gms.common.api.internal.zzbo.zzb(Unknown Source)
                 at com.google.android.gms.common.api.internal.zzbo.zzaiw(Unknown Source)
                 at com.google.android.gms.common.api.internal.zzbo.onConnected(Unknown Source)
                 at com.google.android.gms.common.internal.zzac.onConnected(Unknown Source)
                 at com.google.android.gms.common.internal.zzn.zzakr(Unknown Source)
                 at com.google.android.gms.common.internal.zze.zzw(Unknown Source)
                 at com.google.android.gms.common.internal.zzi.zzaks(Unknown Source)
                 at com.google.android.gms.common.internal.zzh.handleMessage(Unknown Source)
                 at android.os.Handler.dispatchMessage(Handler.java:102)
                 at android.os.Looper.loop(Looper.java:154)
                 at android.os.HandlerThread.run(HandlerThread.java:61)

Here is my Service class. This always runs in the background.

public class LocationService extends Service {


    ...
    ...
    ...

    @Override
    public void onCreate() {
        try {
            mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

            mLocationCallback = new LocationCallback() {
                @Override
                public void onLocationResult(LocationResult locationResult) {
                    super.onLocationResult(locationResult);
                    onNewLocation(locationResult.getLastLocation());
                }
            };

            createLocationRequest();
            getLastLocation();
            requestLocationUpdates();
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Service started");

        return START_STICKY;
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }


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


    @Override
    public void onDestroy() {
        Log.i(TAG, "Service destroy");
        Toast.makeText(getApplicationContext(), "Service Destroy", Toast.LENGTH_SHORT).show();
    }

    /**
     * Makes a request for location updates. Note that in this sample we merely log the
     * {@link SecurityException}.
     */
    public void requestLocationUpdates() {
        Log.i(TAG, "Requesting location updates");
        Utils.setRequestingLocationUpdates(this, true);
        try {

          mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                mLocationCallback, Looper.myLooper());
        } catch (SecurityException unlikely) {
            Utils.setRequestingLocationUpdates(this, false);
            Log.e(TAG, "Lost location permission. Could not request updates. " + unlikely);
        }
    }

    /**
     * Removes location updates. Note that in this sample we merely log the
     * {@link SecurityException}.
     */
    public void removeLocationUpdates() {
        Log.i(TAG, "Removing location updates");
        try {

   mFusedLocationClient.removeLocationUpdates(mLocationCallback);
            Utils.setRequestingLocationUpdates(this, false);
            stopSelf();
        } catch (SecurityException unlikely) {
            Utils.setRequestingLocationUpdates(this, true);
            Log.e(TAG, "Lost location permission. Could not remove updates. " + unlikely);
        }
    }

    private void getLastLocation() {
        try {
            mFusedLocationClient.getLastLocation()
                .addOnCompleteListener(task -> {
                        if (task.isSuccessful() && task.getResult() != null) {
                            mLocation = task.getResult();
                        } else {
                            Log.w(TAG, "Failed to get location.");
                        }
                    });
        } catch (SecurityException unlikely) {
            Log.e(TAG, "Lost location permission." + unlikely);
        }
    }

    private void onNewLocation(Location location) {
        Log.i(TAG, "New location: " + location);

        mLocation = location;
        // Doing some operations using location data.
        mPreviousLocation = mLocation;
        System.gc();

    }

    /**
     * Sets the location request parameters.
     */
    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest
         .setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest
        .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Log.i(TAG, "Service Task Removed");
        Toast.makeText(getApplicationContext(), "Service Task Removed", Toast.LENGTH_SHORT).show();
        startService(new Intent(getApplicationContext(), LocationService.class));
        super.onTaskRemoved(rootIntent);
    }
}

I am checking location permission in my activity class and starting the service once the user accepts the permission. There is no issue with that and I am even getting location updates from background service. Meanwhile when I try to disable the permission from settings the app crashes. Here is my MainActivity class where I am checking the location permission.

public class MainActivity extends AppCompatActivity{


@Override
protected void onResume() {
    super.onResume();
    checkAndEnableGps();
}

private void checkAndEnableGps() {
    try {
        new RxPermissions(this)
                .request(Manifest.permission.ACCESS_FINE_LOCATION)
                .subscribe(granted -> {
                    if (granted) {
                        if (!Utils.isServiceRunning(this, LocationService.class)) {
                            startService(new Intent(this, LocationService.class));
                        }
                        showSettingDialog();
                    } 
                });

    }catch (Exception e){
        e.printStackTrace();
    }
}

}

You are getting an error regarding location access permission denied by the user and you need to check the permission granted by using checkSelfPermission(context, permission) method, this will return the permission grant state and then you can request for permission access from user.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
    mMap.setMyLocationEnabled(true);
} else {
    // Show rationale and request permission.
}

Then use 'onRequestPermissionsResult' callback from the context you requested the permission and start your location service if the permission is granted.

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == MY_LOCATION_REQUEST_CODE) {
      if (permissions.length == 1 &&
          permissions[0] == Manifest.permission.ACCESS_FINE_LOCATION &&
          grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        mMap.setMyLocationEnabled(true);
    } else {
      // Permission was denied. Display an error message.
    }
}

Refer https://developers.google.com/maps/documentation/android-api/location for documentation.

You have to add checkpermission for 6+ ie from Marshmallow like below,

public void checkPermission()
{
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
                   ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
    {

            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},
                    123);
    }
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) {
    checkPermission();
}

Hope it helps!

It needs those permissions, you remove them, so when it tries to get the location it crashes.

Either use a catch statement to handle the exception or check if you have permissions and ask for them an runtime before actually trying to get the location

Just check if you have permission before running the Service.

mContext.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED;

If you don't have the permission you can request for it from either a Fragment or an Activity .

requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION }, 200 /*request code*/);

You can get the user response ie PERMISSION_GRANTED in the Fragment or Activity class.

@Override
public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions, @NonNull int[] grantResults){

    switch(permsRequestCode){

        case 200:    // notice the request code is the same as requested.

            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                startService();
            }
            else {
                // don't start service.
            }

            break;
    }
}

This is happening because since API version 6.0 you need to check permission at run time. And if permission is not granted, your app should ask to grant permission before doing permission related task.

Here, for access user location required permission is ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION . So, you need to check user granted Location permission or not.

One more thing, you are taking location in a service class. Here is a problem -

You cannot ask the user for permission from a service

but you can check it by calling checkSelfPermission()

To make it more nice you should check and request permission in Activity/Fragment class then start Service manually.

On the other hand, you can check for permissions in service, if not accepted by user then show notification, grant permissions and then start Service.

YOU HAVE CHECKED PERMISSION IN AN ACTIVITY:

So, now use checkSelfPermission() to check whether user granted permission or not. If not then show Notification with PendingIntent add some flag with it. Opening of Activity request for permission and then start service by,

startService(new Intent(YourActivity.this, YourService.class));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM