December 24, 2016

In Android 6.0 Marshmallow, application will not be granted at installation time. Instead, application has to ask user for a permission one-by-one at runtime.
From now on, in Marshmallow a dialog for permission will be shown while running the application. That dialog will not be automated, that part is to be done from our side. Suppose, your application wants to read your contact list, then manually you have to call a method, which will prompt a dialog which will ask the user to allow this app the permission to read your contacts. Its upto user now, whether he/she can allow or deny it. If the developer is not handling this method at runtime, then the application will crash on the Marshmallow device. However, the user will be able to revoke the granted permission anytime through phone’s Settings application.

“Hey ! What’s about my application that launched 3 years ago. If it is installed on Android 6.0 device, does this behavior also applied? Will my application also crash?!?”

Don’t worry. Android team has already thought about it. If the application’s targetSdkVersion is set to less than 23. It will be assumed that application is not tested with new permission system yet and will switch to the same old behavior: user has to accept every single permission at install time and they will be all granted once installed !

Below are the list of the permission which will be automatically granted.

There is some permission that will be automatically granted at install time and will not be able to revoke. We call it Normal Permission (PROTECTION_NORMAL). Here is the full list of them:

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT

Just simply declare those permissions in AndroidManifest.xml and it will work just fine. No need to check for the permission listed above since it couldn’t be revoked.

Permissions are grouped into the Permission Group. For Example, android.permission.READ_CALENDAR, and
android.permission.WRITE_CALENDAR,
these two permissions are grouped in android.permission-group.CALENDAR.

If any permission in a Permission Group is granted. Another permission in the same group will be automatically granted as well. In the above example, if READ_CALENDAR is granted, the the application will also grant WRITE_CALENDAR.

 

 

Below is the Code I'm writing to access Location
    public boolean checkLocationPermission() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_FINE_LOCATION)) {
                Toast.makeText(getApplicationContext(), "Necessary Permission", Toast.LENGTH_LONG).show();
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_LOCATION);
            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_LOCATION);
            }
            return false;
        } else {
            return true;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_LOCATION: {
                if (grantResults.lengthgrantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    if (ContextCompat.checkSelfPermission(this,
                            Manifest.permission.ACCESS_FINE_LOCATION)
                            == PackageManager.PERMISSION_GRANTED) {
                        if (mGoogleApiClient == null) {
                            buildGoogleApiClient();
                        }
                        mMap.setMyLocationEnabled(true);
                    }
                } else {
                    Toast.makeText(this, "Permission Denied", Toast.LENGTH_LONG).show();
                }
                return;
            }
        }
    }

If permission has already been granted, then it will work fine. Otherwise, requestPermissions will be called to launch a permission request dialog.

No matter Allow or Deny is chosen, Activity’s onRequestPermissionsResult will always be called to inform a result which we can check from the 3rd parameter, grantResults, like this:

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_PERMISSIONS:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    performTask();
                } else {
                    Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT)
                            .show();
                }
                break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

How to Handle “Never Ask Again”:

If user denied a permission. In the second launch, user will get a “Never ask again” option to prevent application from asking this permission in the future.

If this option is checked before denying, then the next time we call requestPermissions, the dialog for that particular permission will not appear.

If this option is checked before denying. Next time we call requestPermissions, this dialog will not be appeared for this kind of permission anymore. Instead, it just does nothing.

MULTIPLE PERMISSION AT A TIME:

Suppose in your application there are few permission which needs to be granted, and you want to force the user to accept the permission, then below is the Code you need to follow.
// For All Permission

private boolean checkAndRequestPermissions() {
        camera_Permission = ContextCompat.checkSelfPermission(MapsActivity.this,
                android.Manifest.permission.CAMERA);
        location_Permission = ContextCompat.checkSelfPermission(MapsActivity.this,
                android.Manifest.permission.ACCESS_FINE_LOCATION);
        phone_Permission = ContextCompat.checkSelfPermission(MapsActivity.this,
                android.Manifest.permission.READ_PHONE_STATE);
        storageWrite_Permission = ContextCompat.checkSelfPermission(MapsActivity.this,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE);

        List listPermissionsNeeded = new ArrayList();

        if (location_Permission != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(android.Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (camera_Permission != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(android.Manifest.permission.CAMERA);
        }
        if (phone_Permission != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(android.Manifest.permission.READ_PHONE_STATE);
        }

        if (storageWrite_Permission != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded
                    .add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }

        if (!listPermissionsNeeded.isEmpty()) {
            ActivityCompat.requestPermissions(MapsActivity.this, listPermissionsNeeded
                            .toArray(new String[listPermissionsNeeded.size()]),
                    REQUEST_ID_MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    }

    // This will be prompted when the user denied the permission.
    private void showDialogOK(String message,
                              DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(MapsActivity.this).setMessage(message).setCancelable(false)
                .setPositiveButton("OK", okListener).show();
    }


    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_ID_MULTIPLE_PERMISSIONS: {
                Map<String, Integer> perms = new HashMap<String, Integer>();
                // Initialize the map with both permissions
                perms.put(android.Manifest.permission.CAMERA,
                        PackageManager.PERMISSION_GRANTED);
                perms.put(android.Manifest.permission.ACCESS_FINE_LOCATION,
                        PackageManager.PERMISSION_GRANTED);
                perms.put(android.Manifest.permission.READ_PHONE_STATE,
                        PackageManager.PERMISSION_GRANTED);
                perms.put(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        PackageManager.PERMISSION_GRANTED);
                // Fill with actual results from user
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++)
                        perms.put(permissions[i], grantResults[i]);
                    // Check for both permissions
                    if (perms.get(android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
                            && perms.get(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
                            && perms.get(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
                            && perms.get(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                    } else {
                        if (ActivityCompat.shouldShowRequestPermissionRationale(
                                MapsActivity.this, android.Manifest.permission.CAMERA)
                                || ActivityCompat
                                .shouldShowRequestPermissionRationale(
                                        MapsActivity.this,
                                        android.Manifest.permission.ACCESS_FINE_LOCATION)
                                || ActivityCompat
                                .shouldShowRequestPermissionRationale(
                                        MapsActivity.this,
                                        android.Manifest.permission.READ_PHONE_STATE)

                                || ActivityCompat
                                .shouldShowRequestPermissionRationale(
                                        MapsActivity.this,
                                        android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                            showDialogOK("Permission Required For This App",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog,
                                                            int which) {
                                            switch (which) {
                                                case DialogInterface.BUTTON_POSITIVE:

                                                    MapsActivity.this.runOnUiThread(new Runnable() {

                                                        @Override
                                                        public void run() {
                                                            checkAndRequestPermissions();
                                                        }
                                                    });

                                                    break;
                                            }
                                        }
                                    });
                        }
                    }
                }
            }

        }
    }


Use Support Library to make code forward-compatible

Although the code above works perfectly on Android 6.0 Marshmallow. Unfortunate that it will crash on Android pre-Marshmallow since those functions called are added in API Level 23.

The straight way is you can check Build Version with code below.

if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+
} else {
// Pre-Marshmallow
}
But code will be even more complicated. So I suggest you to use some help from Support Library v4 which is already prepared for this thing. Replace those functions with these:

– ContextCompat.checkSelfPermission()

No matter application is run on M or not. This function will correctly return PERMISSION_GRANTEDif the permission is granted. Otherwise PERMISSION_DENIED will be returned.

– ActivityCompat.requestPermissions()

If this function is called on pre-M, OnRequestPermissionsResultCallback will be suddenly called with correct PERMISSION_GRANTED or PERMISSION_DENIED result.

– ActivityCompat.shouldShowRequestPermissionRationale()

If this function is called on pre-M, it will always return false.

PLEASE NOTE : Always try to replace Activity’s checkSelfPermission, requestPermissionsand shouldShowRequestPermissionRationale with these functions from Support Library v4. And your application will work perfectly find on any Android version with same code logic.