Android Runtime Access - Permissions Dispatcher

Posted by gte604j on Mon, 01 Jul 2019 22:36:17 +0200

Some time ago, when I was writing and playing, I used a positioning library, but I always failed to get the location successfully. Instead, I changed my mobile phone and got it again. Considering that the version of two mobile phones can not locate is 6.0, and the other is 4.4, so there should be no permission. This is true when you look at the log, so record the issue of access to runtime permissions after 6.0 (api 23). Although this has been around for a long time, there are still some applications that haven't been dealt with yet. This article is written with the use of Permissions Dispatcher, an open source library.
Github link: hotchemi/PermissionsDispatcher

Differences in access to new and old permissions (in most cases)

Before 6.0

User: When an application is installed, it lists all the required permissions, and confirms that the installation means that the application agrees to obtain these permissions. If a permission user does not want to grant these permissions, the only way is to cancel the installation. The final result may be that a small number of functions require this permission, so that the user can not install and use the permissions. Application, which is unwanted for both sides.
Developer: Declare the required permissions in the Android Manifest file. It's easy to request authorization. What permissions do you need plus uses-permission?

6.0 and beyond

User: Users can operate our application permissions in the settings of mobile phones or in some permission management applications, forbidding them from granting permissions they are unwilling to grant. As a result, they can still install and use applications, and they are more comfortable when using them.
Developer: You need to add code where you need permission to dynamically apply for permission grant. You can't enter the functional module that needs permission until the user agrees. Requesting authorization is a little more complicated than before, but it is more user-friendly.

Permission level

The permissions of the system can be roughly divided into normal permissions, dangerous permissions and special permissions.

  • Normal permissions: Covers areas where applications need to access external data or resources in their sandbox, but have little operational risk to user privacy or other applications. For example, the permission to set a time zone is the normal permission. If an application declares that it needs normal permissions, the system automatically grants that permission to the application.
  • Hazardous permissions: Covers areas where applications need data or resources that involve user privacy information, or may have an impact on data stored by users or operations of other applications. For example, the ability to read a user's contacts is a dangerous permission. If an application declares that it needs dangerous permissions, the user must explicitly grant that permission to the application.
  • Special privileges: There are many privileges whose behavior is different from normal privileges and dangerous privileges. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are particularly sensitive, so most applications should not use them. If an application needs one of these permissions, it must be declared in the list and an intent requesting user authorization must be sent. The system will display a detailed management screen to the user in response to the intent.

Permission Groups

All dangerous Android system privileges belong to the privilege group. If the device runs Android 6.0 (API level 23) and the targetSdkVersion is 23 or higher, the following behavior will occur when the user requests dangerous permissions:

  • If the application requests the dangerous permissions listed in its list and the application does not currently have any permissions in the permission group, the system will display a dialog box to the user describing the permission groups to be accessed by the application. Dialog boxes do not describe specific permissions within this group. For example, if an application requests READ_CONTACTS permissions, the system dialog box only indicates that the application needs to access the contact information of the device. If the user approves, the system grants the application the permissions it requests.

  • If the application requests the dangerous permissions listed in its list and the application already has another dangerous permission in the same permission group, the system grants the permission immediately without any interaction with the user. For example, if an application has requested and granted READ_CONTACTS permission, and then it requests WRITE_CONTACTS, the system will grant that permission immediately.

  • Any permission can belong to a permission group, including normal permissions and application-defined permissions. However, permission groups only affect user experience when permission is dangerous. Permission groups with normal permissions can be ignored.
  • If the device runs Android 5.1 (API level 22) or lower, and the targetSdkVersion is 22 or lower, the system will require users to grant privileges at installation time. Again, it is emphasized that the system only tells the user the permission groups needed by the application, but not the specific permissions.

Dangerous permissions and their permission groups

Permission Groups Jurisdiction
CALENDAR
(Calendar)
READ_CALENDAR
WRITE_CALENDAR
CAMERA
(Camera)
CAMERA
CONTACTS
(Address Book)
READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION
(Geographical location)
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE
(Microphone)
RECORD_AUDIO
PHONE
(Telephone)
READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS
(Sensors)
BODY_SENSORS
SMS
(SMS)
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
STORAGE
(Storage)
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

Use of Permissions Dispatcher

They already have some introductions on GitHub. You can read the README document directly above. Here, I just explain the steps of implementation here, and take a simple call demo as an example. Maybe the writing is somewhat different, but the purpose is to achieve the final grant of privileges.
Design sketch:

Depending on Permissions Dispatcher

Since our project is aimed at users with 6.0 or more systems, we need to change the targetSdkVersion in our project to 23 or more.

Add some code to the Project build.gradle file

buildscript {
    repositories {
        jcenter()
        ...
    dependencies {
        ...
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        ...
    }
}

Add in the build.gradle file of Modele

apply plugin: 'android-apt'
dependencies {
    ...
    /*Later versions view GitHub*/
    compile 'com.github.hotchemi:permissionsdispatcher:2.4.0'
    apt 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
}

Examples and notes

First of all, according to our example, dial the phone, then the required permissions are CALL_PHONE, at which point we still need to add permissions in the Android Manifest file.

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

We write our logic code as usual, and then we apply for permissions where they are needed. Give me the complete code first. Basically, you can draw a ladle according to the gourd. It's very simple to use. Specifically, I'll explain it after the code.

@RuntimePermissions
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button mBtnCallPhone;
    private TextView mTvTelNum;

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

        initView();
    }

    private void initView() {
        mBtnCallPhone = (Button) findViewById(R.id.btn_callPhone);
        mBtnCallPhone.setOnClickListener(this);
        mTvTelNum = (TextView) findViewById(R.id.tv_telNum);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_callPhone:
                //Initiate a permission application with the name "This Activity name + Permissions Dispatcher. Call method name + WithCheck"
                      MainActivityPermissionsDispatcher.callPhoneWithCheck(MainActivity.this);
                callPhone();
                break;
            default:
                break;
        }
    }

    /**
     * Approaches requiring permissions
     */
    @NeedsPermission(Manifest.permission.CALL_PHONE)
    void callPhone(){
        String telNum = mTvTelNum.getText().toString().trim();
        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+telNum));
        try {
            startActivity(intent);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Tips for calling out permissions
     * @param request The right to apply
     */
    @OnShowRationale(Manifest.permission.CALL_PHONE)
    void showRationaleForCallPhone(PermissionRequest request) {
        showRationaleDialog("To use this function, you need to turn on the permission to make a call.", request);
    }

    /**
     * Rejected by User
     */
    @OnPermissionDenied(Manifest.permission.CALL_PHONE)
    void onCallPhoneDenied() {
        Toast.makeText(this,"Privileges are not granted and functions are unavailable",Toast.LENGTH_SHORT).show();
    }

    /**
     * When rejected and checked not to remind authorization, the application needs to prompt the user not to obtain permission, and the user needs to open it in the settings by himself.
     */
    @OnNeverAskAgain(Manifest.permission.CALL_PHONE)
    void onCallPhoneNeverAskAgain() {
        AskForPermission();
    }

    /**
     * Reasons for informing users of their specific needs for permissions
     * @param messageResId
     * @param request
     */
    private void showRationaleDialog(String messageResId, final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setPositiveButton("Determine", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        request.proceed();//Request permission
                    }
                })
                .setNegativeButton("cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage(messageResId)
                .show();
    }

    /**
     * Rejected and no longer reminded, prompting the user to set the interface to reopen permissions
     */
    private void AskForPermission() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Current applications lack dial-up privileges,Please go to the settings interface and open it.");
        builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });
        builder.setPositiveButton("Set up", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.setData(Uri.parse("package:" + getPackageName())); // Open the corresponding setup interface according to the package name
                startActivity(intent);
            }
        });
        builder.create().show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // Use Permissions Dispatcher for permission processing
        MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }
}

The open source library has five annotations, and you can see the significance of these annotations in the examples above.

  • @ Runtime Permissions (must) tag Active or Fragment classes that require runtime privilege judgment and tag them.
  • @ Needs Permission (must) tag methods that require permissions. That is to say, in this method, there is a need to grant permission, and it is executed after granting.
  • @ Callbacks when OnShow Rational requests permissions are generally used to explain why they are needed.
  • @ OnPermissionDenied flags the method that is executed when the user does not grant the permission or when the access permission is denied.
  • @ OnNeverAskAgain executes processing in the method with this tag if the permission request is forbidden by the user and is no longer prompted.

Let's go back to the above permission classification. The dangerous permission can be written as the above example, but the special permissions Manifest.permission.SYSTEM_ALERT_WINDOW and Manifest.permission.WRITE_SETTINGS have been changed a little, but the change is not big, just write the permission callback in the RequonEstPermission Result in onActivity Resu. In Lt.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    MainActivityPermissionsDispatcher.onActivityResult(this, requestCode);
}

Again, the rule for calling methods when using this library is that you (@Runtime Permissions annotated class name) +Permissions Dispatcher. (@Needs Permission annotated method name) +WithCheck (@Runtime Permissions annotated object). For example, here I am:

MainActivityPermissionsDispatcher.callPhoneWithCheck(MainActivity.this);

Note: One thing to note here is that except that the first @Runtime Permissions is annotated on the class, the other four methods that annotate methods cannot be set to private.

All right, here we can run our program on our version of 6.0 and above to check it. Basically, it's OK, and you'll find that this library is really simple to use. It's troublesome to write names like MainActivity Permissions Dispatcher when calling, and it's error-prone, but basically rebuild. You can try, of course, there are many open source libraries for runtime access, but I think this is the simplest. Radish and vegetable have their own preferences, suitable for their own line.

Reference resources:
Android 6.0 Runtime Rights Solution: http://www.jianshu.com/p/d4a9855e92d3
Privilege Best Practices
System permissions

Topics: Android github Mobile Gradle