Runtime Permission from Android Marshmallow (6.0)

    Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. This approach streamlines the app install process, since the user does not need to grant permissions when they install or update the app. It also gives the user more control over the app's functionality; for example, a user could choose to give a camera app access to the camera but not to the device location. The user can revoke the permissions at any time, by going to the app's Settings screen.
    Android system permissions are divided into many categories. But when it comes to permission request at runtime from Android 6.0 onward, system permissions are categorized into two categories: Normal and Dangerous:
  • Normal permissions do not directly risk the user's privacy. If your app lists a normal permission in its manifest, the system grants the permission automatically. List of normal permission can be found HERE.
  • Dangerous permissions can give the app access to the user's confidential data. If your app lists a normal permission in its manifest, the system grants the permission automatically. If you list a dangerous permission, the user has to explicitly give approval to your app. List of dangerous permissions is available HERE.
    By this post, I would like to present the way to request Runtime permissions with dangerous permissions in Android M. I will reuse AndroidDownloadManagerSample project from my previous post which talked about DownloadManager.
    Firstly, just look at AndroidManifest.xml file of this project:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.devexchanges.downloadingfile">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
    As you can see, first permission android.permission.INTERNET is a normal permission and the seconds one android.permission.WRITE_EXTERNAL_STORAGE is a dangerous permission. Therefore in the following example, we would request the user to grant it at runtime.

Check and request a runtime permission

    Firstly, determine whether if the permission is already available with checkSelfPermission() method of ActivityCompat class, then if permission has not been granted, prompt user by use requestPermissions() method:
private void requestStoragePermission() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE);
        } else {
            Log.i("Main", "Storage permissions have already been granted. Download the file");
            downloadFile();
        }
    }
    When running this project, you'll have this alert dialog:

Handle user action after request permission

    User can click "Allow" or "Deny" on the alert dialog. To handle this action, you must override onRequestPermissionsResult(). In this project, if user granted the permission, an IMG file will be downloaded to your device:
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_STORAGE) {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                downloadFile();
            } else  {
                Toast.makeText(this, "You now can not download the file!", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void downloadFile() {
        downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        Request request = new Request(Uri.parse(DOWNLOAD_URL));

        //Get download file name
        String fileExtenstion = MimeTypeMap.getFileExtensionFromUrl(DOWNLOAD_URL);
        String name = URLUtil.guessFileName(DOWNLOAD_URL, null, fileExtenstion);

        //Save file to destination folder
        request.setDestinationInExternalPublicDir("/Downloads", name);
        enqueue = downloadManager.enqueue(request);
    }
    Of course, the permission request will be invoked when you click the "Start Download" button, so you call requestStoragePermission() method here. Remember to check Build.VERSION_CODES to request this runtime permission:
public void onClick(View view) {
        int currentApiVersion = android.os.Build.VERSION.SDK_INT;
        if (currentApiVersion >= Build.VERSION_CODES.M) {
            requestStoragePermission();
        } else {
            downloadFile();
        }
    }
    Running this project, If user denied the permission, app will show a Toast:
    And if user granted this permission, your file will be downloaded by DownloadManager:

    Moreover, user also can grant runtime permission for your app in Settings of their device:

Conclusions

    With runtime permission, Android M and later is more secure. From now, if your app have one or more dangerous permission, you must prompt user to request it at the runtime, you don't have any another choice. Only thing you could do right now is to make our application fully support this new permission system. Hope this post is helpful with your work, for full project code, please click the button below.

Share


Previous post
« Prev Post
Next post
Next Post »