美文网首页
Android 权限管理--04:AppOps机制

Android 权限管理--04:AppOps机制

作者: DarcyZhou | 来源:发表于2023-12-20 08:29 被阅读0次

    本文转载自:Android Framework权限篇四之AppOps机制

    本文基于Android 9.0源码分析

    1.概述

      在前面《Android 权限管理--03:后台定位权限源码分析》这篇文章中介绍到了AppOps机制的一个重要功能是track,举的具体例子是和电量耗电相关的定位权限,限制应用退到后台之后访问位置。这篇文章继续展开讲AppOps的access control。

    /**
    * App-ops are used for two purposes: Access control and tracking.
    *
    * <p>App-ops cover a wide variety of functionality from helping with runtime permissions access
    * control and tracking to battery consumption tracking.
    

    2.WRITE_SETTINGS权限

      首先,我们看下WRITE_SETTINGS这个权限,平时如果三方应用需要申请此权限的时候需要如下操作:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (!Settings.System.canWrite(MainActivity.this)) {
            Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
            intent.setData(Uri.parse("package:" + getPackageName()));
            //启动指定Action的权限管理应用的弹窗Activity
            startActivity(intent);
        } else {
            //如有该权限可以直接设置屏幕亮度
            setLight();
        }
    }
    
    权限管理4-1.PNG

    然后就会出现如上弹窗选择,这里的权限申请流程和之前的runtime运行时权限不一样,走的是Appops检查机制。首先看下这个权限在framework中的定义,可以看到是signature|appop级别的。

    • signature级别:表示有系统platform系统签名的应用如果声明会默认授权;

    • appops级别:表示可以通过appops机制授权也就是上面的弹窗授权方式。

    // framework/base/core/res/AndroidManifest.xml
        <permission android:name="android.permission.WRITE_SETTINGS"
            android:label="@string/permlab_writeSettings"
            android:description="@string/permdesc_writeSettings"
            android:protectionLevel="signature|preinstalled|appop|pre23" />
    

    这里从Settings.System.canWrite()往下看源码是如何做检查的?这里往下会走到isCallingPackageAllowedToPerformAppOpsProtectedOperation()。

    // frameworks/base/core/java/android/provider/Settings.java
    public static boolean isCallingPackageAllowedToPerformAppOpsProtectedOperation(Context context,int uid, String callingPackage, boolean throwException, int appOpsOpCode, String[]
            permissions, boolean makeNote) {
        if (callingPackage == null) {
            return false;
        }
        //1.获取AppOpsManager
        AppOpsManager appOpsMgr = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
        int mode = AppOpsManager.MODE_DEFAULT;
        //2.检查AppOpsManager.OP_WRITE_SETTINGS权限授权值
        if (makeNote) {
            mode = appOpsMgr.noteOpNoThrow(appOpsOpCode, uid, callingPackage);
        } else {
            mode = appOpsMgr.checkOpNoThrow(appOpsOpCode, uid, callingPackage);
        }
        //3.根据授权值返回结果
        switch (mode) {
            case AppOpsManager.MODE_ALLOWED:
                return true;
            case AppOpsManager.MODE_DEFAULT:
                for (String permission : permissions) {
                    if (context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
                        return true;
                    }
                }
            default:
                // this is for all other cases trickled down here...
                if (!throwException) {
                    return false;
                }
        }
    

    整体流程如下:这里补充下,检查mode值时,这里WRITE_SETTINGS权限在AppOps中的默认值是MODE_DEFAULT;此时再进一步通过context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED检查,如果是系统platform签名应用signature权限会默认授权,返回true,如果是三方应用则返回false。

    权限管理4-2.png

    3.Appops实现

    3.1 权限状态值

    // frameworks/base/core/java/android/app/AppOpsManager.java
    
    //允许
    public static final int MODE_ALLOWED = 0;
    //拒绝
    public static final int MODE_IGNORED = 1;
    //拒绝并抛出异常
    public static final int MODE_ERRORED = 2;
    //default模式,会进一步check权限,如上面的"修改系统设置"权限
    public static final int MODE_DEFAULT = 3;
    //前台模式,主要是后台位置权限使用,可以结合第三篇权限文章食用,有例子用到
    public static final int MODE_FOREGROUND = 4;
    

    3.2 权限定义值

      这里关于appops权限定义的官方介绍是每个runtime permission权限值都会有对应的app op值对应,如下就是罗列了各个权限op对应的值。

    <h3>Runtime permissions and app-ops</h3>
    
    <p>Each platform defined runtime permission (beside background modifiers) has an associated app
    op which is used for tracking but also to allow for silent failures.
    
    public static final int OP_COARSE_LOCATION = 0;
    /** @hide Access to fine location information. */
    @UnsupportedAppUsage
    public static final int OP_FINE_LOCATION = 1;
    /** @hide Causing GPS to run. */
    @UnsupportedAppUsage
    public static final int OP_GPS = 2;
    /** @hide */
    @UnsupportedAppUsage
    public static final int OP_VIBRATE = 3;
    /** @hide */
    @UnsupportedAppUsage
    public static final int OP_READ_CONTACTS = 4;
    /** @hide */
    @UnsupportedAppUsage
    public static final int OP_WRITE_CONTACTS = 5;
    ...
    

    3.3 数据结构

      AppOpsService.java中通过SparseArray<UidState> mUidStates来维护状态,key值为uid,value值为对应的UidState,相关类基本结构如下:

    权限管理4-3.png

    对应的持久化文件位置是在/data/system/appops.xml 文件内容如下:

    • uid标签:用于记录和 uid 相关的限制信息;

    • pkg标签:用于记录和具体应用的限制信息;

    • pkg标签下的uid标签的n:表示pkg所属的uid;

    • pkg标签下的uid标签的p:表示是否是privileged应用;

    • n:表示权限值;

    • m:表示权限的授权值;

    • st:标签下的是对应的权限的拒绝时间,允许时间,持续使用时间等。

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <app-ops>
    <uid n="10210">
    <op n="0" m="1" />
    <op n="87" m="0" />
    </uid>
    ...
    <pkg n="com.android.recentspsp">
    <uid n="1000" p="true">
    <op n="3">
    <st n="214748364801" t="1606363097865" d="50" pu="0" />
    </op>
    </uid>
    </pkg>
    ...
    

    通过如下adb命令可以看到相应的信息:

    adb shell dumpsys appops
    

    如这里的COARSE_LOCATION在 2020-11-23 11:24:50.018时检查权限是允许的:

    权限管理4-4.png

    关于AppOpsManager中也有一些重要的变量和方法整理如下:这里面有一个sOpDefaultMode数组,表示每个权限op都有对应的默认授权mode值。

    权限管理4-5.png

    3.4 Appops api

      appops调用主要是通过AppOpsManager提供的api方法进行操作:

    权限管理4-6.png

    (1)检查权限:AppOpsManager.checkOp()最终会调用到AppOpsService.checkOperationUnchecked()

    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,boolean raw) {
        ...
            //1\. 获取对应权限的op值
            code = AppOpsManager.opToSwitch(code);
            //2\. 获取uid对应的UidState
            UidState uidState = getUidStateLocked(uid, false);
            //3\. 如果在uid标签下有对应的值则直接返回,android10这里的raw为false(主要增加前后台判断)
            if (uidState != null && uidState.opModes != null
                    && uidState.opModes.indexOfKey(code) >= 0) {
                final int rawMode = uidState.opModes.get(code);
                return raw ? rawMode : uidState.evalMode(code, rawMode);
            }
            //4\. 如果没有从uid标签下没有取到值,则会从pkg标签下取pkg对应的Op
            Op op = getOpLocked(code, uid, packageName, false, false);
            //5 如果也取不到的话,则直接返回该权限的默认状态值
            if (op == null) {
                return AppOpsManager.opToDefaultMode(code);
            }
            return raw ? op.mode : op.evalMode();
        }
    }
    

    (2)检查权限并记录:AppOpsManager.noteOp()最终会调用到AppOpsService.noteOperationUnchecked()

    final Ops ops = getOpsRawLocked(uid, packageName, isPrivileged, true /* edit */);
    ...
    final Op op = getOpLocked(ops, code, true);
    ...
    final UidState uidState = ops.uidState;
    ...
    final int switchCode = AppOpsManager.opToSwitch(code);
    ...
    //1\. 如果uid标签下有对应的值则读取
    if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
        final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
        //1.1 如果拒绝的话,则直接返回,并且更新拒绝时间
        if (uidMode != AppOpsManager.MODE_ALLOWED) {
           ...
           op.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
                    uidState.state, flags);
            mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
                    uidState.state, flags);
            scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
            return uidMode;
        }
    } else {
    //2\. uid标签下无的话从pkg标签下读取
        final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
        final int mode = switchOp.evalMode();
        //2.2 如果拒绝的话,同样直接返回,并更新拒绝时间
        if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
            ...
            op.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
                    uidState.state, flags);
            mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
                    uidState.state, flags);
            scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
            return mode;
        }
    }
    //3\. 走到这里则为允许,并且更新允许时间
    op.accessed(System.currentTimeMillis(), proxyUid, proxyPackageName,
            uidState.state, flags);
    mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
            uidState.state, flags);
    scheduleOpNotedIfNeededLocked(code, uid, packageName,
            AppOpsManager.MODE_ALLOWED);
    return AppOpsManager.MODE_ALLOWED;
       }
    }
    

    (3)设置权限值:AppOpsService.setUidMode()

    public void setUidMode(int code, int uid, int mode) {
      final int defaultMode = AppOpsManager.opToDefaultMode(code);
      UidState uidState = getUidStateLocked(uid, false);
      if (uidState == null) {
          if (mode == defaultMode) {
              return;
          }
          uidState = new UidState(uid);
          uidState.opModes = new SparseIntArray();
          uidState.opModes.put(code, mode);
          mUidStates.put(uid, uidState);
          scheduleWriteLocked();
      } else if (uidState.opModes == null) {
          if (mode != defaultMode) {
              uidState.opModes = new SparseIntArray();
              uidState.opModes.put(code, mode);
              scheduleWriteLocked();
          }
      } else {
          if (uidState.opModes.indexOfKey(code) >= 0 && uidState.opModes.get(code) == mode) {
              return;
          }
          if (mode == defaultMode) {
              uidState.opModes.delete(code);
              if (uidState.opModes.size() <= 0) {
                  uidState.opModes = null;
              }
          } else {
              uidState.opModes.put(code, mode);
          }
          scheduleWriteLocked();
      }
      uidState.evalForegroundOps(mOpModeWatchers);
    

    (4)监听权限变化: 如以下是监听悬浮窗权限状态变化;悬浮窗权限类似如上的修改系统设置权限,声明的级别也是appops权限,这里添加的监听表示如果开关了权限,需要更新app状态,有可能app悬浮窗正在使用。

    // frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
    
    mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
    AppOpsManager.OnOpChangedInternalListener opListener =
            new AppOpsManager.OnOpChangedInternalListener() {
                @Override public void onOpChanged(int op, String packageName) {
                    updateAppOpsState();
                }
            };
    mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener);
    
    <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
            android:label="@string/permlab_systemAlertWindow"
            android:description="@string/permdesc_systemAlertWindow"
            android:protectionLevel="signature|preinstalled|appop|pre23|development" />
    

    相关文章

      网友评论

          本文标题:Android 权限管理--04:AppOps机制

          本文链接:https://www.haomeiwen.com/subject/mluhidtx.html