Android Marshmallow Runtime Permission Example

Android permissions have been divided into two groups from android 6.0 which build name is android marshmallow. And the related SDK version is 23. This article will tell you the difference between those groups with examples.

1. Android Permission Groups.

1.1 Normal Permissions.

  1. One group of permission is called normal permissions which do not need users to allow at runtime,  you just need to declare them in the AndroidManifest.xml file as usual.
  2. All the permissions except runtime permissions are all normal permissions.

1.2 Dangerous ( Runtime ) Permissions

  1. Another group of permission is called dangerous permissions, they are all related to android OS core operations.
  2. They also need to be declared in the AndroidManifest.xml file, and they need users to allow when the app runs. This can give app users the choice of android permission selection.
  3. The android dangerous permissions are divided into groups, if you allow one permission in a group, then all the group permissions are granted to the android application. That means the android app can use all the permissions in the group.

2. Android Runtime Permission WorkFlow.

  1. If you run an app with an android SDK version smaller than 23, even your app requires dangerous ( runtime ) permissions, the app does not need the user to grant the required permission to run.
  2. But if the SDK version is bigger than 23 then when the app requires dangerous permissions, then android os will popup a window to let the app user to choose whether grant permission to the app or not.
    android-runtime-permissions-process-diagram

2.1 Android Runtime Permission Check And Require Code.

  1. Use the below code to check whether the required permission has been granted to the current app or not.
    ContextCompat.checkSelfPermission(context, runtimePermission) == PackageManager.PERMISSION_GRANTED
  2. If required runtime permission is not granted, use the below code to require the app user to allow it.
    ActivityCompat.requestPermissions(activity, new String[]{runtimePermission}, requestCode);
  3. Overwrite the below method in your activity, and in this method, you can get the user grant result. The requestCode input parameter is just the requestCode you used in step 2.
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
        // If this is our permission request result.
        if(requestCode==PERMISSION_REQUEST_CODE)
        {
    
        }
    }
  4. If you do not require the dangerous permissions in SDK version 23 or higher before you use, then you will see the below exceptions.
    java.lang.SecurityException: Permission Denial: starting Intent
  5. After you uninstall the android app, all the permissions granted to it will be removed also.

3. Android Runtime Permission Example.

If you can not watch the above video, you can see it on the youtube URL https://youtu.be/QkuIiu_baHg

  1. Because there are three normal android permissions declared in the AndroidManifest.xml file, so those permissions can be listed when you click the first button.
  2. If you want to manually deny runtime permissions that have been granted to the app. You can follow the below steps.
  3. Click Settings —> Apps.
  4. Click the application you want to operate.
  5. Click Permissions menu item.
  6. Toggle the permissions button to manually turn on / off related permissions.

If you can not watch the above video, you can see it on the youtube URL https://youtu.be/F9NHg8GZKhA

3.1 Android Manifest Xml File.

  1. AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.dev2qa.example">
    
        <!-- Normal android permissions. -->
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    
        <!-- Dangerous Runtime Permissions. -->
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.CALL_PHONE" />
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            
            <activity android:name=".permission.runtime.RuntimePermissionActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
        </application>
    
    </manifest>

3.2 Activity Java Code.

  1. RuntimePermissionActivity.java
    package com.dev2qa.example.permission.runtime;
    
    import android.Manifest;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Bundle;
    import android.support.annotation.NonNull;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.content.ContextCompat;
    import android.support.v7.app.AlertDialog;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.Toast;
    
    import com.dev2qa.example.R;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class RuntimePermissionActivity extends AppCompatActivity {
    
        private static final String TAG_RUNTIME_PERMISSION = "TAG_RUNTIME_PERMISSION";
    
        private static final int PERMISSION_REQUEST_CODE = 1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_runtime_permission);
    
            setTitle("dev2qa.com - Android Runtime Permission Example");
    
            // List all granted permissions.
            Button listGrantedPermissionButton = (Button)findViewById(R.id.runtime_permission_list_granted);
            listGrantedPermissionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    // First get package name.
                    String packageName = getPackageName();
                    List<String> permissionList = getAllPermissions(packageName);
    
                    StringBuffer grantedPermissionBuf = new StringBuffer();
    
                    grantedPermissionBuf.append("Package ");
                    grantedPermissionBuf.append(packageName);
                    grantedPermissionBuf.append(" has been granted below permissions : \r\n\r\n");
    
                    int size = permissionList.size();
                    for(int i=0;i<size;i++)
                    {
                        String permission = permissionList.get(i);
                        // If this permission has been granted.
                        if(hasRuntimePermission(getApplicationContext(), permission)) {
                            grantedPermissionBuf.append(permissionList.get(i));
    
                            if (i < size - 1) {
                                grantedPermissionBuf.append(",\r\n\r\n");
                            }
                        }
                    }
    
                    // Show user granted permissions list in a alert dialog.
                    AlertDialog alertDialog = new AlertDialog.Builder(RuntimePermissionActivity.this).create();
                    alertDialog.setMessage(grantedPermissionBuf.toString());
                    alertDialog.show();
                }
            });
    
            // Process camera permission.
            Button cameraPermissionButton = (Button)findViewById(R.id.runtime_permission_camera);
            cameraPermissionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!hasRuntimePermission(getApplicationContext(), Manifest.permission.CAMERA)) {
                        requestRuntimePermission(RuntimePermissionActivity.this, Manifest.permission.CAMERA, PERMISSION_REQUEST_CODE);
                    } else {
                        Toast.makeText(RuntimePermissionActivity.this, "Manifest.permission.CAMERA permission already has been granted", Toast.LENGTH_LONG).show();
                    }
                }
            });
    
            // Process accounts permission.
            Button accountsPermissionButton = (Button)findViewById(R.id.runtime_permission_accounts);
            accountsPermissionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!hasRuntimePermission(getApplicationContext(), Manifest.permission.GET_ACCOUNTS)) {
                        requestRuntimePermission(RuntimePermissionActivity.this, Manifest.permission.GET_ACCOUNTS, PERMISSION_REQUEST_CODE);
                    } else {
                        Toast.makeText(RuntimePermissionActivity.this, "Manifest.permission.GET_ACCOUNTS permission already has been granted", Toast.LENGTH_LONG).show();
                    }
                }
            });
    
            // Process gps location permission.
            Button locationPermissionButton = (Button)findViewById(R.id.runtime_permission_location);
            locationPermissionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!hasRuntimePermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION)) {
                        requestRuntimePermission(RuntimePermissionActivity.this, Manifest.permission.ACCESS_FINE_LOCATION, PERMISSION_REQUEST_CODE);
                    } else {
                        Toast.makeText(RuntimePermissionActivity.this, "Manifest.permission.ACCESS_FINE_LOCATION permission already has been granted", Toast.LENGTH_LONG).show();
                    }
                }
            });
    
            // Process write external storage permission.
            Button storagePermissionButton = (Button)findViewById(R.id.runtime_permission_storage);
            storagePermissionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!hasRuntimePermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                        requestRuntimePermission(RuntimePermissionActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_REQUEST_CODE);
                    } else {
                        Toast.makeText(RuntimePermissionActivity.this, "Manifest.permission.WRITE_EXTERNAL_STORAGE permission already has been granted", Toast.LENGTH_LONG).show();
                    }
                }
            });
    
    
            // Process phone call permission.
            Button phoneCallPermissionButton = (Button)findViewById(R.id.runtime_permission_phone_call);
            phoneCallPermissionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!hasRuntimePermission(getApplicationContext(), Manifest.permission.CALL_PHONE)) {
                        requestRuntimePermission(RuntimePermissionActivity.this, Manifest.permission.CALL_PHONE, PERMISSION_REQUEST_CODE);
                    } else {
                        Toast.makeText(RuntimePermissionActivity.this, "Manifest.permission.CALL_PHONE permission already has been granted", Toast.LENGTH_LONG).show();
                        makePhoneCall("10086");
                    }
                }
            });
        }
    
        // Make a phone call.
        private void makePhoneCall(String phoneNumber)
        {
            // Prepare phone call string uri.
            String phoneCallString = "tel:"+phoneNumber;
            Uri phoneCallUri = Uri.parse(phoneCallString);
    
            // Prepare phone call intent.
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(phoneCallUri);
    
            // Start phone call activity and make phone call.
            try {
                startActivity(intent);
            }catch(SecurityException ex)
            {
                Log.e(TAG_RUNTIME_PERMISSION, "makePhoneCall: " + ex.getMessage(), ex);
            }
        }
    
        // This method is used to check whether current app has required runtime permission.
        private boolean hasRuntimePermission(Context context, String runtimePermission)
        {
            boolean ret = false;
    
            // Get current android os version.
            int currentAndroidVersion = Build.VERSION.SDK_INT;
    
            // Build.VERSION_CODES.M's value is 23.
            if(currentAndroidVersion > Build.VERSION_CODES.M)
            {
                // Only android version 23+ need to check runtime permission.
                if(ContextCompat.checkSelfPermission(context, runtimePermission) == PackageManager.PERMISSION_GRANTED)
                {
                    ret = true;
                }
            }else
            {
                ret = true;
            }
            return ret;
        }
    
        /* Request app user to allow the needed runtime permission.
           It will popup a confirm dialog , user can click allow or deny. */
        private void requestRuntimePermission(Activity activity, String runtimePermission, int requestCode)
        {
            ActivityCompat.requestPermissions(activity, new String[]{runtimePermission}, requestCode);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
            // If this is our permission request result.
            if(requestCode==PERMISSION_REQUEST_CODE)
            {
                if(grantResults.length > 0)
                {
                    // Construct result message.
                    StringBuffer msgBuf = new StringBuffer();
                    int grantResult = grantResults[0];
                    if(grantResult==PackageManager.PERMISSION_GRANTED)
                    {
                        msgBuf.append("You granted below permissions, you can do the action again to use the permission : ");
                    }else
                    {
                        msgBuf.append("You denied below permissions : ");
                    }
    
                    // Add granted permissions to the message.
                    if(permissions!=null)
                    {
                        int length = permissions.length;
                        for(int i=0;i<length;i++)
                        {
                            String permission = permissions[i];
                            msgBuf.append(permission);
    
                            if(i<length-1) {
                                msgBuf.append(",");
                            }
                        }
                    }
    
                    // Show result message.
                    Toast.makeText(getApplicationContext(), msgBuf.toString(), Toast.LENGTH_LONG).show();
                }
            }
        }
    
    
        // Return all this app required permissions defined in AndroidManifest.xml file..
        private List<String> getAllPermissions(String packageName)
        {
            List<String> ret = new ArrayList<String>();
            try {
    
                // Get package info.
                PackageManager packageManager = getPackageManager();
                PackageInfo packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
    
                // Loop to add all package requested permissions to string list.
                int length = packageInfo.requestedPermissions.length;
                for(int i=0;i<length;i++)
                {
                    String requestPermission = packageInfo.requestedPermissions[i];
                    ret.add(requestPermission);
                }
            }catch (PackageManager.NameNotFoundException ex)
            {
                Log.e(TAG_RUNTIME_PERMISSION, "getAllGrantedPermissions: " + ex.getMessage(), ex);
            }finally {
                return ret;
            }
        }
    }

3.3 Layout XML File.

  1. activity_runtime_permission.xml
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/runtime_permission_list_granted"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="List Granted Permissions"/>
    
        <Button
            android:id="@+id/runtime_permission_camera"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Use Camera"/>
    
        <Button
            android:id="@+id/runtime_permission_accounts"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Get Accounts"/>
    
        <Button
            android:id="@+id/runtime_permission_location"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Get Location"/>
    
        <Button
            android:id="@+id/runtime_permission_storage"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Write External Storage"/>
    
        <Button
            android:id="@+id/runtime_permission_phone_call"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Make Phone Call"/>
    
    </LinearLayout>

References

  1. How To Change Minimum SDK Version In Android Studio
  2. Android ADB Install / Uninstall App Examples

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.