1. 前言
在项目开发中,需求:app中有恢复出厂设置的功能,分解这个需求的时候,第一反应肯定不是第三方app,恢复出厂设置肯定需要有系统权限,属于系统级的app。然后在看手机系统中的功能,恢复出厂设置功能属于设置模块,找到源码阅读,当然是能借用的代码就拿过来直接用了。一段操作猛如虎,然后烧写版本无法开机,瞬间石化~, 怎么办,还是看log分析原因吧。
2. log分析
06-23 14:49:09.414 5472 5472 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
06-23 14:49:09.414 5472 5472 E AndroidRuntime: java.lang.IllegalStateException: Signature|privileged permissions not in privapp-permissions whitelist: {com.test.mtk: android.permission.MASTER_CLEAR}
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService.systemReady(PermissionManagerService.java:2967)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService.access$100(PermissionManagerService.java:122)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService$PermissionManagerServiceInternalImpl.systemReady(PermissionManagerService.java:3028)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.pm.PackageManagerService.systemReady(PackageManagerService.java:21995)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.SystemServer.startOtherServices(SystemServer.java:2104)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.SystemServer.run(SystemServer.java:529)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.server.SystemServer.main(SystemServer.java:366)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:503)
06-23 14:49:09.414 5472 5472 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:914)
Signature|privileged permissions not in privapp-permissions whitelist: {com.test.mtk: android.permission.MASTER_CLEAR}
如果要恢复出厂设置,则需要在AndroidManifest.xml文件中配置
<uses-permission android:name="android.permission.MASTER_CLEAR" />
而且我也配置了 android:sharedUserId="android.uid.system" 表示属于系统级别的app, 但还是报这个错误, 意思是android.permission.MASTER_CLEAR此权限没有配置到 特许权限白名单中。
3. 需求分解
2.1 首先我们来看设置模块恢复出厂设置的代码:
# packages/apps/Settings/src/com/android/settings/MasterClearConfirm.java
private void doMasterClear() {
Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
intent.setPackage("android");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
intent.putExtra(Intent.EXTRA_WIPE_ESIMS, mEraseEsims);
getActivity().sendBroadcast(intent);
}
就是发送一个带有Intent.ACTION_FACTORY_RESET 属性的广播给系统, 系统服务端frameworks/base/services/core/java/com/android/server/MasterClearReceiver.java 接收到广播后做恢复出厂的任务,大致是这么一个流程。
2.2 接下来我们在看看Intent.ACTION_FACTORY_RESET 属性的定义
/**
* A broadcast action to trigger a factory reset.
*
* <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
* reason for the factory reset should be specified as {@link #EXTRA_REASON}.
*
* <p>Not for use by third-party applications.
*
* @see #EXTRA_FORCE_FACTORY_RESET
*
* {@hide}
*/
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";
看注释我们知道,使用此action 必须要申请MASTER_CLEAR权限,不能用于第三方app。 好接下来我们去看看MASTER_CLEAR权限定义的地方
2.3 所有权限定义的地方在 frameworks/base/core/res/AndroidManifest.xml 文件中
<!-- Not for use by third-party applications. -->
<permission android:name="android.permission.MASTER_CLEAR"
android:protectionLevel="signature|privileged" />
从定义的地方,此权限的保护级别为 signature|privileged ,第一点需要有系统签名, 第二点需要放置到手机 */priv-app/ 分区下,才可以正常使用该权限。通过这两点,我们就知道自己的app要放置到手机的哪个分区了。我工程的Android.mk 文件如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MANIFEST_FILE := AndroidManifest.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
LOCAL_PACKAGE_NAME := TestApp
#app编译生成路径:product/priv-app
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/priv-app
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PRIVATE_PLATFORM_APIS := true
#系统签名
LOCAL_CERTIFICATE := platform
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.core_core \
androidx.appcompat_appcompat
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
把工程代码放到系统源码中编译,最后app生成路径为: product/priv-app/TestApp/
4. 源码看根因
3.1 好了接下来,我们通过log找到源码报错的地方
#/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
private void systemReady() {
mSystemReady = true;
// mPrivappPermissionsViolations 不为空,就抛出异常
if (mPrivappPermissionsViolations != null) {
throw new IllegalStateException("Signature|privileged permissions not in "
+ "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
}
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
}
通过log查看堆栈 或者 Android studio 断点调试 流程图如下:
SystemServer, PMS , PermissionManagerService 都是系统核心的服务,当走到PermissionManagerService.SystemReady()方法这里的时候,直接抛出一个异常,导致系统没有正常启动,就无法顺利开机。
3.2 我们在看看判断条件 mPrivappPermissionsViolations != null 时才会抛出异常,接下来继续分析mPrivappPermissionsViolations是在哪里赋值的
#/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
BasePermission bp, PermissionsState origPermissions) {
boolean oemPermission = bp.isOEM();
boolean vendorPrivilegedPermission = bp.isVendorPrivileged();
boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged();
boolean privappPermissionsDisable =
RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
&& !platformPackage && platformPermission) {
if (!hasPrivappWhitelistEntry(perm, pkg)) {
// Only report violations for apps on system image
if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
// it's only a reportable violation if the permission isn't explicitly denied
ArraySet<String> deniedPermissions = null;
if (pkg.isVendor()) {
deniedPermissions = SystemConfig.getInstance()
.getVendorPrivAppDenyPermissions(pkg.packageName);
} else if (pkg.isProduct()) {
#走这里的分支 deniedPermissions 值为null
deniedPermissions = SystemConfig.getInstance()
.getProductPrivAppDenyPermissions(pkg.packageName);
} else if (pkg.isProductServices()) {
deniedPermissions = SystemConfig.getInstance()
.getProductServicesPrivAppDenyPermissions(pkg.packageName);
} else {
deniedPermissions = SystemConfig.getInstance()
.getPrivAppDenyPermissions(pkg.packageName);
}
#通过打印log: deniedPermissions == null 为true, 说明权限白名单中没有配置MASTER_CLEAR权限,这里是问题的原因。
final boolean permissionViolation =
deniedPermissions == null || !deniedPermissions.contains(perm);
#这里的判断条件为true
if (permissionViolation) {
Slog.w(TAG, "Privileged permission " + perm + " for package "
+ pkg.packageName + " - not in privapp-permissions whitelist");
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
if (mPrivappPermissionsViolations == null) {
mPrivappPermissionsViolations = new ArraySet<>();
}
//添加包名和权限名,所以mPrivappPermissionsViolations有值,不为空
mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
}
} else {
return false;
}
}
我把app放置到product/priv-app 路径下,所以走的逻辑为:
else if (pkg.isProduct()) {
deniedPermissions = SystemConfig.getInstance()
.getProductPrivAppDenyPermissions(pkg.packageName);
}
继续跟踪代码,找到SystemConfig.java 中的 getProductPrivAppDenyPermissions 方法
public ArraySet<String> getProductPrivAppDenyPermissions(String packageName) {
return mProductPrivAppDenyPermissions.get(packageName);
}
接下来,我们继续看mProductPrivAppDenyPermissions变量是怎么赋值的, 我们回到SystemConfig.java 的构造方法:
#frameworks/base/core/java/com/android/server/SystemConfig.java
SystemConfig() {
// Read configuration from system
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
// Read configuration from the old permissions dir
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
// Vendors are only allowed to customize these
int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
| ALLOW_ASSOCIATIONS;
if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) {
// For backward compatibility
vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
}
readPermissions(Environment.buildPath(
Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
// Allow ODM to customize system configs as much as Vendor, because /odm is another
// vendor partition other than /vendor.
int odmPermissionFlag = vendorPermissionFlag;
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
String skuProperty = SystemProperties.get(SKU_PROPERTY, "");
if (!skuProperty.isEmpty()) {
String skuDir = "sku_" + skuProperty;
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "sysconfig", skuDir), odmPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "permissions", skuDir),
odmPermissionFlag);
}
// Allow OEM to customize these
int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS | ALLOW_ASSOCIATIONS;
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "permissions"), oemPermissionFlag);
// Allow Product to customize all system configs
readPermissions(Environment.buildPath(
Environment.getProductDirectory(), "etc", "sysconfig"), ALLOW_ALL);
readPermissions(Environment.buildPath(
Environment.getProductDirectory(), "etc", "permissions"), ALLOW_ALL);
// Allow /product_services to customize all system configs
readPermissions(Environment.buildPath(
Environment.getProductServicesDirectory(), "etc", "sysconfig"), ALLOW_ALL);
readPermissions(Environment.buildPath(
Environment.getProductServicesDirectory(), "etc", "permissions"), ALLOW_ALL);
}
这段代码的大致工作:就是读取手机中 system/etc/permissions/ product/etc/permissions/ odm/etc/permissions/ vendor/ete/permissons/ 路径下的权限配置.xml 文件,如下:
如果该路径下没有log中提及的 com.test.mtk.xml 文件,就满足上面源码分析的条件,就抛异常导致系统无法正常开机启动。
5. 解决方案
4.1 我的app是放置在手机系统product/priv-app 路径下的, 对应的特许权限白名单文件也应该放置到product/etc/permissions/ 路径下 怎么修改呢?我是直接通过Andorid.mk 文件实现的
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MANIFEST_FILE := AndroidManifest.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
LOCAL_PACKAGE_NAME := TestApp
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/priv-app
######编译priv-app 权限到apk中###########
LOCAL_REQUIRED_MODULES := com.test.mtk.xml
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.core_core \
androidx.appcompat_appcompat
include $(BUILD_PACKAGE)
######预编译priv-app 权限,输出路径为product/etc/permissions###########
# Permissions pre-grant
include $(CLEAR_VARS)
LOCAL_MODULE := com.test.mtk.xml
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc/permissions
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
include $(call all-makefiles-under,$(LOCAL_PATH))
com.test.mtk.xml 内容:
<?xml version="1.0" encoding="utf-8"?>
<permissions>
<privapp-permissions package="com.test.mtk">
<permission name="android.permission.MASTER_CLEAR" />
</privapp-permissions>
</permissions>
4.2 假如你的app打算编译到手机的system/priv-app 路径下,则配置的特许权限白名单要对应放置到system/etc/permissions/ 路径下,则有两种修改方法:
第一种方案 : 修改 framework/base/data/etc/privapp-permissions-platform.xml 把自己的权限加入其中,它最后编译后输出的路径为:/system/etc/permissions/, 是由Android.bp实现
# framework/base/data/etc/Android.bp
prebuilt_etc {
name: "privapp-permissions-platform.xml",
sub_dir: "permissions",
src: "privapp-permissions-platform.xml",
}
第二种方案: 还是通过Android.mk 文件实现,稍微修改一下路径就可以了:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MANIFEST_FILE := AndroidManifest.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
LOCAL_PACKAGE_NAME := TestApp
#app编译到system/priv-app/路径下
LOCAL_MODULE_PATH := $(TARGET_OUT)/priv-app
######编译priv-app 权限到apk中###########
LOCAL_REQUIRED_MODULES := com.test.mtk.xml
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.core_core \
androidx.appcompat_appcompat
include $(BUILD_PACKAGE)
######预编译priv-app 权限,输出路径为system/etc/permissions###########
# Permissions pre-grant
include $(CLEAR_VARS)
LOCAL_MODULE := com.test.mtk.xml
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
include $(call all-makefiles-under,$(LOCAL_PATH))
推荐这种方案,原因:特许权限白名单(com.test.mtk.xml)是与app的源码在一起,便于代码移植,也算是间接的解耦了。 如果是用第一种方案,你做的功能,如果换一个人来维护,这个privapp-permissions-platform.xml 修改很容易漏掉就又无法开机了。