在Android 10.0系统定制化开发过程中,我们遇到了一个典型的权限管控问题:第三方应用调用系统灭屏接口时,由于系统强制要求DEVICE_POWER权限,导致灭屏功能失效。这个问题在需要实现自动化灭屏的场景中尤为突出,比如智能家居控制、企业设备管理等应用场景。
Android系统从早期版本就开始对电源管理相关操作进行严格管控,这是出于安全考虑。DEVICE_POWER权限属于系统级签名权限(protectionLevel="signature"),这意味着只有使用系统签名打包的应用才能获得该权限。这种设计虽然保障了系统安全,但也限制了合法第三方应用的合理需求。
重要提示:修改系统权限管控属于深度定制行为,仅适用于自有设备系统开发场景,普通应用开发者应通过标准API实现功能需求。
面对这个需求,我们评估了三种技术方案:
方案A:修改应用签名
方案B:使用反射绕过检查
方案C:修改PowerManagerService
经过综合评估,我们选择了方案C作为最终实施方案,主要基于以下考虑:
具体实现将围绕PowerManagerService展开,主要修改点包括:
灭屏功能的核心实现在以下类中:
code复制frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
具体涉及的方法调用链:
code复制PowerManager.goToSleep()
→ PowerManagerService.goToSleep()
→ PowerManagerService.goToSleepInternal()
在PowerManagerService.goToSleepInternal()方法中,存在如下权限检查代码:
java复制private void goToSleepInternal(long eventTime, int reason, int flags, int uid) {
// 权限检查逻辑
if (uid != Process.SYSTEM_UID) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
}
// 后续灭屏逻辑...
}
这段代码的含义是:
我们采用最小化修改原则,在原有安全框架内进行调整:
方案一:完全移除检查(不推荐)
java复制// 直接注释掉权限检查代码
// mContext.enforceCallingOrSelfPermission(...);
方案二:条件式绕过(推荐)
java复制if (uid != Process.SYSTEM_UID && !isTrustedApp(uid)) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
}
其中isTrustedApp()需要实现白名单机制:
java复制private boolean isTrustedApp(int uid) {
// 实现自己的白名单逻辑
// 可以基于包名、签名或配置列表
}
安全建议:生产环境强烈建议采用方案二,配合白名单机制可以平衡功能需求与系统安全。
下载对应Android 10.0源码
bash复制repo init -u https://android.googlesource.com/platform/manifest -b android-10.0.0_r41
repo sync
配置编译环境
bash复制source build/envsetup.sh
lunch aosp_arm-eng
java复制// 在类成员变量区添加
private static final Set<String> TRUSTED_PACKAGES = new ArraySet<>();
static {
TRUSTED_PACKAGES.add("com.example.trustedapp");
}
// 添加白名单检查方法
private boolean isTrustedApp(int uid) {
String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
if (packages != null) {
for (String pkg : packages) {
if (TRUSTED_PACKAGES.contains(pkg)) {
return true;
}
}
}
return false;
}
// 修改goToSleepInternal方法
private void goToSleepInternal(long eventTime, int reason, int flags, int uid) {
if (uid != Process.SYSTEM_UID && !isTrustedApp(uid)) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
}
// 原有逻辑保持不变...
}
全量编译系统
bash复制make -j8
刷入测试设备
bash复制adb reboot bootloader
fastboot flashall -w
添加CTS测试用例:
java复制@Test
public void testGoToSleepWithoutPermission() {
PowerManager pm = mContext.getSystemService(PowerManager.class);
try {
pm.goToSleep(SystemClock.uptimeMillis());
// 对于修改后的系统,应该不会抛出异常
} catch (SecurityException e) {
fail("Should not throw SecurityException after modification");
}
}
| 测试场景 | 预期结果 |
|---|---|
| 系统进程调用 | 正常灭屏 |
| 白名单应用调用 | 正常灭屏 |
| 普通应用调用 | 根据配置决定行为 |
| 恶意应用调用 | 不应影响系统安全 |
java复制Slog.i(TAG, "goToSleep called by uid=" + uid + " packages=" + Arrays.toString(packages));
问题1:修改后灭屏仍然无效
问题2:系统稳定性受影响
问题3:权限绕过过于宽松
替代硬编码白名单,可以通过SettingsProvider实现动态配置:
定义配置项:
xml复制<string-array name="config_powerWhitelist">
<item>com.example.trustedapp</item>
</string-array>
修改检查逻辑:
java复制private boolean isTrustedApp(int uid) {
String[] whitelist = mContext.getResources().getStringArray(
com.android.internal.R.array.config_powerWhitelist);
// 检查逻辑...
}
更安全的实现方式是创建权限代理服务:
定义AIDL接口:
java复制interface IPowerPermissionProxy {
boolean canBypassPowerPermission(int uid);
}
实现代理服务:
java复制public class PowerPermissionService extends IPowerPermissionProxy.Stub {
@Override
public boolean canBypassPowerPermission(int uid) {
// 实现精细化的权限控制逻辑
}
}
修改原始检查:
java复制if (uid != Process.SYSTEM_UID && !mPowerPermissionProxy.canBypassPowerPermission(uid)) {
enforcePermission(...);
}
对于高频调用的场景:
我在实际项目中发现,这类系统级修改需要特别注意兼容性和可维护性。建议在实现功能的同时:
对于企业级设备管理场景,还可以考虑结合DevicePolicyManager实现更精细的电源控制策略,这需要同时修改多个系统服务,但可以提供更完整的管理能力。