Runtime permissions and Proxi.Cloud SDK

Android 6 (API 23) introduced runtime permissions, which means that user decieds whether some permissions should be granted during application’s runtime. User is presented with a system popup and can grant or deny permission. Only permissions labeled as dangerous need to be handled this way, location permissions are a part of this group.

Proxi.cloud SDK requires location permissions for the majority of functionalities to work properly, specifically ACCES_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION (introduced in Android 10).

Important!

Google Play Store requires apps with ACCESS_BACKGROUND_LOCATION permission to fill aditional permission declaration form. Check this page before you start implementing.

Requesting theese permissions has become quite a chore since changes introduced in Android 10, 11 and because of new Google Play Store policies regarding background location access. In this section we are going to present you a simple implementation.

To learn more about location permissions and runtime permissions in general you can visit android developer docs: Requesting location permissions and Requesting runtime permissions .

Requesting location permissions implementation

Before we start requesting any location permissions we should display information to a user why we request location permission, that we access location while app is not in use and that we use this data to provide personalised ads.

Example:

locationPermissionPrompt

In this example the prompt is implemented as a separate Activity but depending on the architecture of your app it may be a Dialog, DialogFragment, Fragment or a custom View.

On Android 11 (API 30) and above We also need a second dialog that describes in detail features gained by allowing background location access. It also must have a button that directs user to application’s location settings.

Example:

backgroundLocationPermissionPrompt

Activity implementation:

class LocationRequestActivity extends AppCompatActivity {

    static final int LOCATION_PERMISSION_REQUEST_CODE = 100;
    static final int BACKGROUND_LOCATION_PERMISSION_API_30_REQUEST_CODE = 200;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_location_permission);

        allowButton.setOnClickListener(v -> {
            requestPermissions(LOCATION_PERMISSION_REQUEST_CODE);
        });

        backButton.setOnClickListener(v -> {
            closePrompt();
        });
    }

    private void closePrompt() {
        finish();
    }

    //helper function
    private boolean checkSinglePermission(String permission) {
        return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED;
    }
    
    private void requestLocationPermissions(int locationRequestCode) {
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
            requestLocationPermissionApi29(locationRequestCode);
        } else {
            requestFineLocationPermission(locationRequestCode);
        }
    }

    /**
        Request ACCESS_FINE_LOCATION permission, it allows to access location both in background
        and foreground on Android versions up to Android 9 (API 28)

        For Android 11 (API 30) and above it will only allow to access location in foreground.
        Background location access must be requested separately.
    **/
    private void requestFineLocationPermission(int locationRequestCode) {
        if (checkSinglePermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
            return;
        }
        String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
        ActivityCompat.requestPermissions(this, permissions, locationRequestCode);
    }

    /**
        Separate function for Android 10 (API 29). Unlike Android 11, in this version you can ask for 
        foreground and background location access in one request.

        Be careful as requesting both permissions in one call on Android 11 will make the system ignore the whole request!
    **/
    @RequiresApi(api = Build.VERSION_CODES.Q)
    private void requestLocationPermissionApi29(int locationRequestCode) {
        if (checkSinglePermission(Manifest.permission.ACCESS_FINE_LOCATION) 
        && checkSinglePermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
            return;
        }
        String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION};
        ActivityCompat.requestPermissions(this, permissions, locationRequestCode);
    }

    /**
        Request background location separatly on devices with Android 11 (API 30).
        It presents user with a dialog explaining why background location access is needed.
        Clicking allow directs user to app's permission settings.
        Call this function only if ACCESS_FINE_LOCATION permission has been granted.
    **/
    private void requestBackgroundLocationPermissionApi30(int backgroundLocationRequestCode) {
        if (checkSinglePermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
            return;
        }
        new AlertDialog.Builder(this)
                .setTitle("Update location settings")
                .setMessage("Message explaining why app requires background location access")
                .setPositiveButton("Update settings", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //this code will open app's settings so user can grant background location permission
                        String[] permissions = new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION};
                        ActivityCompat.requestPermissions(LocationRequestActivity.this, permissions, backgroundLocationRequestCode);
                    }
                })
                .setNegativeButton("No, thanks", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        closePrompt();
                    }
                })
                .create()
                .show();
    }
    
    /**
        Handle permission results
    **/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case LOCATION_PERMISSION_REQUEST_CODE:
                for (int i = 0; i < permissions.length; i++) {
                     String permission = permissions[i];
                     boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
                    if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION) && granted) {
                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
                            //foreground location has been granted, ask user for access to location in background
                            requestBackgroundLocationPermissionApi30(BACKGROUND_LOCATION_PERMISSION_API_30_REQUEST_CODE);
                        }
                    }
                }
                break;
            case BACKGROUND_LOCATION_PERMISSION_API_30_REQUEST_CODE:
                closePrompt();
                break;
        }
    }
}