Skip to content

特殊权限

解释 Android 17 悬浮窗、修改设置、未知来源安装和全文件访问的 AppOps 检查路径。

基于android-17.0.0_r1
AndroidAppOps特殊权限PermissionChecker源码阅读

特殊权限 ​

本文承接 权限类型、权限检查 和 AppOps 与权限。特殊权限不是一组统一的 requestPermissions() 弹窗权限;它们通常以 app-op protection flag、专用 Settings 页面、AppOps mode 或服务端策略组合实现。本文以 Android 17 的 SYSTEM_ALERT_WINDOW、WRITE_SETTINGS、REQUEST_INSTALL_PACKAGES、MANAGE_EXTERNAL_STORAGE 为例,追踪“Manifest 声明 → Settings 开关 → AppOps → Window/Settings/PackageInstaller/Storage 服务”的真实边界。

1. 特殊权限模型 ​

1.1 不同 owner ​

能力Manifest permission主要 AppOp最终消费者
悬浮窗SYSTEM_ALERT_WINDOWOP_SYSTEM_ALERT_WINDOWWindowManagerService
修改设置WRITE_SETTINGSOP_WRITE_SETTINGSSettings provider/调用 API
未知来源安装REQUEST_INSTALL_PACKAGESOP_REQUEST_INSTALL_PACKAGESPackageInstaller/PMS
全文件访问MANAGE_EXTERNAL_STORAGEOP_MANAGE_EXTERNAL_STORAGE存储服务

Manifest 只声明资格;用户在 Settings 中改变 AppOps mode;最终服务在真正操作时再次检查。不同能力的 AppOp 可能按 package 或 UID 保存,不能假设所有特殊权限使用同一个设置 API。

2. AppOps 映射 ​

2.1 操作常量 ​

源码文件:frameworks/base/core/java/android/app/AppOpsManager.java

java
public static final int OP_WRITE_SETTINGS =
        AppOpEnums.APP_OP_WRITE_SETTINGS;
public static final int OP_SYSTEM_ALERT_WINDOW =
        AppOpEnums.APP_OP_SYSTEM_ALERT_WINDOW;
public static final int OP_REQUEST_INSTALL_PACKAGES =
        AppOpEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
public static final int OP_MANAGE_EXTERNAL_STORAGE =
        AppOpEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;

操作码来自生成的 AppOpEnums,不能把旧版本数值硬编码进文章或测试。permissionToOpCode 是稳定的映射入口。

2.2 映射函数 ​

java
public static int permissionToOpCode(String permission) {
    final Integer boxedOpCode = sPermToOp.get(permission);
    if (boxedOpCode != null) {
        return boxedOpCode;
    }
    if (permission != null &&
            HealthConnectManager.isHealthPermission(
                    ActivityThread.currentApplication(), permission)) {
        return OP_READ_WRITE_HEALTH_DATA;
    }
    return OP_NONE;
}

无映射返回 OP_NONE,不代表特殊权限自动允许;调用者必须根据 permission protection 决定是否继续 hard permission 检查。Health permission 有额外动态映射。

3. 悬浮窗 ​

3.1 Context 便捷 API ​

源码文件:frameworks/base/core/java/android/provider/Settings.java

java
public static boolean canDrawOverlays(Context context) {
    return Settings.isCallingPackageAllowedToDrawOverlays(
            context, Process.myUid(),
            context.getOpPackageName(), false)
            || context.checkSelfPermission(
                    Manifest.permission.SYSTEM_APPLICATION_OVERLAY)
                == PackageManager.PERMISSION_GRANTED;
}

canDrawOverlays 不只检查 SYSTEM_ALERT_WINDOW permission flags,还调用 Settings 内部的 AppOps/策略判断;system application overlay permission 是独立的系统能力。应用调用该便捷 API 不能推断 WindowManager 一定接受任意窗口类型。

3.2 Settings 页面 ​

源码文件:frameworks/base/core/java/android/provider/Settings.java

java
public static final String ACTION_MANAGE_OVERLAY_PERMISSION =
        "android.settings.action.MANAGE_OVERLAY_PERMISSION";
public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION =
        "android.settings.MANAGE_APP_OVERLAY_PERMISSION";

普通应用可使用 ACTION_MANAGE_OVERLAY_PERMISSION 引导用户;指定包的受保护 action 需要 INTERNAL_SYSTEM_WINDOW。Settings 页面改变的是 AppOps mode,不是 Manifest 或 permission definition。

3.3 窗口消费 ​

WindowManagerService 在添加 TYPE_APPLICATION_OVERLAY 窗口时再次验证 caller UID/package 和 OP_SYSTEM_ALERT_WINDOW。因此 Settings 开关、便捷 API 返回值和最终 addWindow 结果是三次可能不同的观察点;设备策略、用户限制和 package visibility 都可能让最终调用失败。

4. 修改系统设置 ​

4.1 canWrite ​

源码文件:frameworks/base/core/java/android/provider/Settings.java

java
public static boolean canWrite(Context context) {
    return isCallingPackageAllowedToWriteSettings(
            context, Process.myUid(),
            context.getOpPackageName(), false);
}

Settings.System.canWrite 通过 Settings 内部检查包名、UID 和 OP_WRITE_SETTINGS;仅在 Manifest 声明 WRITE_SETTINGS 仍不够,用户必须在 ACTION_MANAGE_WRITE_SETTINGS 页面授予能力。

4.2 AppOp 默认模式 ​

源码文件:frameworks/base/core/java/android/app/AppOpsManager.java

java
new AppOpInfo.Builder(OP_WRITE_SETTINGS,
        OPSTR_WRITE_SETTINGS, "WRITE_SETTINGS",
        AppOpsManager.MODE_ERRORED)
        .setPermission(android.Manifest.permission.WRITE_SETTINGS)
        .build();

WRITE_SETTINGS 的 AppOp 默认 mode 是 errored;Settings 用户开关或受信系统策略必须显式改变 mode。MODE_DEFAULT 的实际处理由 AppOps/PermissionChecker 继续回退到 permission 检查。

5. 未知来源安装 ​

5.1 Settings action ​

java
public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES =
        "android.settings.MANAGE_UNKNOWN_APP_SOURCES";

应用通过该 action 引导用户为特定 package 开启安装未知来源能力;PackageInstaller 在创建/提交 session 时还会检查 installer identity、用户确认和设备策略。REQUEST_INSTALL_PACKAGES 的 AppOp 默认也是 errored。

5.2 AppOp 定义 ​

java
new AppOpInfo.Builder(OP_REQUEST_INSTALL_PACKAGES,
        OPSTR_REQUEST_INSTALL_PACKAGES,
        "REQUEST_INSTALL_PACKAGES", MODE_ERRORED)
        .setSwitchCode(OP_REQUEST_INSTALL_PACKAGES)
        .setPermission(
                android.Manifest.permission.REQUEST_INSTALL_PACKAGES)
        .build();

该操作属于 package-level app-op permission 列表,用户开关针对 package/UID 组合保存。即使 AppOp allowed,PMS 仍会检查 session 来源和安装权限。

6. 全文件访问 ​

6.1 AppOp 定义 ​

java
new AppOpInfo.Builder(OP_MANAGE_EXTERNAL_STORAGE,
        OPSTR_MANAGE_EXTERNAL_STORAGE,
        "MANAGE_EXTERNAL_STORAGE", MODE_ERRORED)
        .setPermission(
                android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
        .build();

MANAGE_EXTERNAL_STORAGE 使用 AppOp 表达用户授予的全文件访问能力。实际存储 API 还会结合 target SDK、分区存储、文件路径和 MediaProvider 策略;AppOps allowed 不是任意路径都可写的保证。

6.2 权限检查层 ​

PermissionChecker 对带 app-op protection 的权限先映射 op,再执行 operation transaction;MODE_IGNORED 或 MODE_ERRORED 会阻止或降级,MODE_DEFAULT 才回退到普通 permission check。存储服务可能把 soft denied 转成空结果或受限路径。

7. 失败边界 ​

7.1 非弹窗路径 ​

特殊权限通常不由 Activity.requestPermissions 弹窗授予。即使 permission 是 app-op 类型,普通 runtime grant API 也不等价于 Settings 专用开关;调用方应使用对应 Settings action 或专用 manager API。

7.2 用户与策略 ​

AppOps mode 按 package/UID/user 保存。用户限制、设备所有者、系统默认策略和 role 可能覆盖用户开关;检查时必须使用正确 userId 与 op package name。

7.3 权限与 op ​

permission flags granted、AppOps errored 是合法状态。PMS/PermissionChecker 可能返回 permission granted,但 WindowManager、Settings provider、PackageInstaller 或存储服务仍拒绝操作。

8. 验证方法 ​

8.1 悬浮窗 ​

声明 SYSTEM_ALERT_WINDOW,在 Settings 页面切换开关,分别调用 Settings.canDrawOverlays 和添加 TYPE_APPLICATION_OVERLAY 窗口。对照 AppOps mode allowed/ignored/errored,确认最终服务检查独立存在。

8.2 修改设置与安装 ​

声明 WRITE_SETTINGS、REQUEST_INSTALL_PACKAGES,分别使用对应 Settings action 开关,再执行真实 API/PackageInstaller session。关闭开关时应得到拒绝;仅保留 Manifest 声明不能通过。

8.3 全文件访问 ​

在允许和拒绝 MANAGE_EXTERNAL_STORAGE 的 user 下访问受保护路径,比较 AppOps 与存储服务结果。确认 mode allowed 仍受路径和 target SDK 限制。

bash
adb shell appops get <package.name>
adb shell appops set <package.name> SYSTEM_ALERT_WINDOW allow
adb shell appops set <package.name> WRITE_SETTINGS errored
adb shell dumpsys package <package.name>

9. 源码路线 ​

  1. AppOpsManager 的 op 常量与 AppOpInfo:确认特殊权限映射和默认 mode。
  2. Settings.canDrawOverlays/canWrite:查看便捷 API 的额外策略。
  3. PermissionCheckerService.checkAppOpPermission/checkRuntimePermission:理解 mode 与 permission 的组合。
  4. WindowManagerService:追踪悬浮窗最终消费。
  5. PackageInstaller 未知来源检查:连接 REQUEST_INSTALL_PACKAGES 与安装 session。
  6. Storage/MediaProvider:继续阅读 MANAGE_EXTERNAL_STORAGE 的资源服务策略。

特殊权限的本质是把“是否拥有 permission 资格”和“这一次具体操作是否允许”拆开:Settings 或专用策略写入 AppOps mode,PermissionChecker 组合 permission 与 op,最终资源服务再执行自己的用户、路径和前台约束。遇到悬浮窗、修改设置、未知来源安装或全文件访问问题时,必须同时检查 Manifest、permission flags、AppOps mode、user/package identity 和最终服务入口。