Prompting for location permission when NOT monitoring in background?

WallyHale picture WallyHale · Oct 15, 2015 · Viewed 15.4k times · Source

I'm using the Android Beacon Library. Since Marshmallow, I am seeing the following error, as expected and documented.

Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results

I have my ranging code in a Fragment, and the RuntimeException is thrown when the Fragment is on screen, as expected as I'm not prompting for permission. If I hit the home button, Pause is called, and I'm unbinding beaconManager, and the exception is no longer thrown, again, as expected. Coming back to Activity from previous app list, the RuntimeException is thrown again each scan.

Why is the exception thrown when in the foreground even though I have added the permission to the AndroidManifest?

According to the documentation HERE, you only need to prompt the user for location permission if you are performing a scan in the background?

you'll get the following error in LogCat when you try to do a bluetooth scan in the background, and no beacons will be detected

Does this mean ONLY a background scan, or am I misinterpreting the documentation, and you must prompt regardless? I want to avoid the extra (off-putting perhaps to nervous users) prompt for permission inside the app if avoidable!!

My Manifest contains -

<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>

My snipped code from the Fragment is -

public class NearMeNowFragment extends Fragment implements BeaconConsumer {

//Beacon manager stuff
private BeaconManager beaconManager;

<SNIP>


@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    beaconManager = BeaconManager.getInstanceForApplication(getActivity());
    beaconManager.getBeaconParsers().add(new BeaconParser().
            setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.near_me_now, container, false);

    //Set up beacon stuff if Bluetooth available
    if (verifyBluetooth(false)) {
        beaconManager.bind(this);
    }

    <SNIP>

    return rootView;
}


/***************************
Beacon config methods
****************************
*/

@Override
public void onBeaconServiceConnect() {
    //update all scan periods
    beaconManager.setForegroundScanPeriod(1100l);
    beaconManager.setForegroundBetweenScanPeriod(8900l);
    beaconManager.setAndroidLScanningDisabled(true);
    try {
        beaconManager.updateScanPeriods();
    } catch (RemoteException e) {
        e.printStackTrace();
    }

    beaconManager.setRangeNotifier(new RangeNotifier() {
        @Override
        public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
            if (beacons.size() > 0) {
                <SNIP>
                //handling beacons here
                ...
            } else {
                Log.i(TAG, "ZERO BEACONS IN SCAN");
            }
        }
    });

    try {
        beaconManager.startRangingBeaconsInRegion(new Region(TAG, null, null, null));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

@Override
public Context getApplicationContext() {
    return getActivity().getApplicationContext();
}

@Override
public void unbindService(ServiceConnection serviceConnection) {
    getActivity().unbindService(serviceConnection);
}

@Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int mode) {
    return getActivity().bindService(intent, serviceConnection, mode);
}


@Override
public void onPause() {
    super.onPause();

    if(beaconManager.isBound(this)){
        beaconManager.unbind(this);
    }
}

@Override
public void onResume() {
    super.onResume();

    beaconManager.bind(this);
}

@Override
public void onDestroy() {
    super.onDestroy();
    if(beaconManager.isBound(this)){
        beaconManager.unbind(this);
    }
}

}

My actual Logcat error is

W/Binder﹕ Caught a RuntimeException from the binder stub implementation. java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results

Answer

davidgyoung picture davidgyoung · Oct 15, 2015

To clarify the requirement is different depending on whether you set targetSdkVersion 23 in your build.gradle file.

If you target SDK 23 (Marshmallow) or higher:

  • You must declare android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION in your manifest.
  • You must prompt a user for permission as described in my blog post here.
  • The user must accept this permission and not turn it off.
  • If any of the above requirements are not met, you won't be able to detect beacons in the foreground or the background.

If you target SDK 22 or lower:

  • You may optionally declare android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION in your manifest.
  • The user may turn on or off the permission by going to settings.
  • If one of the permissions is not granted, you will still be able to detect beacons in the foreground, but not in the background.

See here and here for more details.