Android 6.0 runtime permissions

1. Android 6.0 runtime permissions

        Before Android 6.0, in a general sense, if the permission was registered in the Manifest, the permission was enabled by default during the installation process, and it could not be turned off after that. This method is quite unsafe, especially when it is possible to access sensitive information. With the arrival of Android 6.0, in order to solve such unsafe problems, permissions can be turned on and off in the system settings, and the declaration in the Manifest is just a permission application "intent", it just gives a list to tell the system that I may need these permissions . At runtime, some of these permissions are disabled by default with higher security levels, so you need to ask to enable or disable them at runtime.

        The new permission mechanism better protects the privacy of users. Google divides permissions into two categories. One is normal permissions. These permissions generally do not involve user privacy and do not require user authorization, such as cell phone vibration and network access. etc.; the other type is risk permission, which generally involves user privacy and requires user authorization, such as reading sdcard, accessing address book, etc.

 

Second, the problem faced - system fragmentation

        Android is an open system. Since the update control of the system version is not unique to Google, the ROMs designed by other ROM manufacturers have more or less compatibility problems. Xiaomi android 6.0, vivo, oppo permission detection is not provided by Google, but through AppOpsManager. In addition, the detection results are also Xiaomi's own [asking mode, mode value is 4]. The most pitiful thing is that vivo android 7.0 can bypass AppOpsManager and checkPermission when accessing the address book, and directly prompt when the cursor is queried. When the permission is denied, the cursor does not trigger an exception. If there is no contact in the personal address book, you will never know that the authorization is successful. No. Other mobile phones also have the problem of timing and automatic prohibition of permissions, which are all places that need to be paid attention to.

 

3. Permission detection method

Permission detection method, Google provides a relatively complete detection mechanism, of course, the open source EasyPremission is relatively simple, and AndPermission is relatively better.

public final class PermissionChecker {
    /** 已授权 */
    public static final int PERMISSION_GRANTED =  PackageManager.PERMISSION_GRANTED;

    /** 拒绝授权 */
    public static final int PERMISSION_DENIED =  PackageManager.PERMISSION_DENIED;

    /** 权限允许,权限操作被拒 */
    public static final int PERMISSION_DENIED_APP_OP =  PackageManager.PERMISSION_DENIED  - 1;

    @IntDef({PERMISSION_GRANTED,
            PERMISSION_DENIED,
            PERMISSION_DENIED_APP_OP})
    @Retention(RetentionPolicy.SOURCE)
    public @interface PermissionResult {}

    private PermissionChecker() {
        /* do nothing */
    }

    /**
     * 检查指定Uid和pid进程的app权限
     * 
     *
     * @param context Context 上下文
     * @param permission  权限名称 如Manifest.permission.READ_CONTACTS【注意:可以是权限组】
     * @param pid 被检测app的进程id
     * @param uid 被检测app的uid
     * @param packageName 被检测app的包名
     * @return 返回结果 {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
     */
    public static int checkPermission(@NonNull Context context, @NonNull String permission,
            int pid, int uid, String packageName) {
        if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
            return PERMISSION_DENIED;
        }

        String op = AppOpsManagerCompat.permissionToOp(permission);
        if (op == null) {
            return PERMISSION_GRANTED;
        }

        if (packageName == null) {
            String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
            if (packageNames == null || packageNames.length <= 0) {
                return PERMISSION_DENIED;
            }
            packageName = packageNames[0];
        }

        if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
                != AppOpsManagerCompat.MODE_ALLOWED) {
            return PERMISSION_DENIED_APP_OP;
        }

        return PERMISSION_GRANTED;
    }

    /**
     * 检测当前app的权限
     *
     * @param context 上下文资源
     * @param permission 权限名称,也可以是权限组
     * @return 返回结果 {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
     */
    public static int checkSelfPermission(@NonNull Context context,
            @NonNull String permission) {
        return checkPermission(context, permission, android.os.Process.myPid(),
                android.os.Process.myUid(), context.getPackageName());
    }

    /**
     * 检测其他app是否具有通过ipc调用本应用的权限
     *
     * @param context 上下文
     * @param permission 要检查的权限
     * @param packageName 调用者的包名,如果是null,则默认根据uid获取包名中的第一个包名
     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
     */
    public static int checkCallingPermission(@NonNull Context context,
            @NonNull String permission, String packageName) {
        if (Binder.getCallingPid() == Process.myPid()) {  //
            return PackageManager.PERMISSION_DENIED;
        }
        return checkPermission(context, permission, Binder.getCallingPid(),
                Binder.getCallingUid(), packageName);
    }

    /**
     * 检查是否所有应用和当前应用具有调用自身或者其他应用的权限
     *
     * @param context 上下文
     * @param permission 要检查的权限
     * @return 返回值 {@link #PERMISSION_GRANTED}
     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
     */
    public static int checkCallingOrSelfPermission(@NonNull Context context,
            @NonNull String permission) {
        String packageName = (Binder.getCallingPid() == Process.myPid())
                ? context.getPackageName() : null;
        return checkPermission(context, permission, Binder.getCallingPid(),
                Binder.getCallingUid(), packageName);
    }
}

4. Permission application

Permission application is the most complicated process, and this process involves the issue of [System Authorization Dialog Box]. Here we first discuss the should ShowRequestPermissionRationale method.

The usage of this method cannot be handled according to the example on Google's official website, otherwise you may enter a logical trap . Let's analyze the return value first.

ActivityCompat.shouldShowRequestPermissionRationale(Context context,
            String permisssion)

The return value has the following situations

  • 1. If the permission is not enabled and the permission has not been applied for, the return value is false
  • 2. If the permission is not enabled, if the permission has been applied for and the permission is denied, but the [Remember not to prompt] is not selected, it will return true
  • 3. If the permission is not enabled, if the permission has been applied for, the permission is denied. If you select [Remember not to prompt again], it will return false
  • 4. The permission is enabled and the authorization is successful, and the return bit is false.

So how to use this method correctly?

The correct way is to use it in onRequestPermissionsResult, we can store the returned result in the boolean value of SharedPreferences. Then take out this boolean value for judgment when we apply for permission.

Note: Even if the cache is cleared, the result will still be called back to onRequestPermissionsResult if the permission is requested again.

Apply for authorization:


if (PermissionChecker.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

     String op = AppOpsManagerCompat.permissionToOp(Manifest.permission.READ_CONTACTS);
    boolean hasNext = ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            perm);
     boolean isShowRequestTip =  !TextUtils.isEmpty(op) && SharePrefUtils.getBoolean(op,true) || hasNext ;
    //注意,SharePrefUtils默认值为true,首次应该返回true
    if (isShowRequestTip ) {  
        
      ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

    } else {

      showDialogForSystem();// 通过自己定义的dialog引导用户去设置页面授权
       
    }
}else{

    Toast.make(thisActivity,"已授权,可以调用通讯录",Toast.LENGTH_SHORT).show();
}

 

Note: Considering that it is possible to turn off authorization on the settings page after user authorization, it is more appropriate to make two sets of local and system judgments for the requested permissions above. The code is as follows:

    boolean hasNext = ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            perm);
     boolean isShowRequestTip =  !TextUtils.isEmpty(op) && SharePrefUtils.getBoolean(op,true) || hasNext ;

 

Handling authorization results:

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
      
       if(MY_PERMISSIONS_REQUEST_READ_CONTACTS!=requestCode) return;
       if(permissions==null || permissions.length==0) return;
       
       //在小米,vivo中,grantResults并不可靠,因此我们还需要使用PermissionChecker进行检测

       List<String> grantList = new ArrayList<>();
       List<String> diniedList = new ArrayList<>();
      
      for(int i=0;i<permissions.length;i++){

         String perm = permissions[i];
         if(PermissionChecker.checkSelfPermission(thisActivity,perm)){
            grantList.add(perm );
        }else{
           diniedList .add(perm );
        }
       
        boolean hasNext = ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            perm);
       
        String op = AppOpsManagerCompat.permissionToOp(perm);
        if(TextUtils.isEmpty(op)) continue;

        SharePrefUtil.setBoolean(op,hasNext);  //记录下次是否允许再次申请
          
        
     }
   
    onPermissionGrants(grantList );  //分别处理不同的权限
    onPermissionDenied(diniedList );
}

 

5. Supplement

The above is only related to the Android 6.0 system, and there are still many areas that need to be improved:

  • The permission detection before Android 6.0 should be authorized by default
  • Fragment or Context detection is not implemented
  • Xiaomi and vivo address book are compatible, Xiaomi can't ask, we have to let him go to the settings page, vivo phone can bypass the detection, this is also a problem that needs to be dealt with.

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325346744&siteId=291194637