问题初探
测试命令: run gts -m GtsGmscoreHostTestCases -t com.google.android.gts.devicepolicy.managedprovisioning.DeviceOwnerProvisioningHostsideTest#testRequiredAppsInManagedDevice
报错堆栈
07-18 16:53:12 I/XtsHostTestBase: Test com.google.android.gts.playstore.ResetPreferredAppsTest#testPersistDefaultBrowser: PASSED
07-18 16:53:19 I/XtsHostTestBase: Test com.google.android.gts.managedprovisioning.AfwRequiredAppsTest#testRequiredApps_DeviceOwner_withGms: FAILURE
07-18 16:53:19 W/XtsHostTestBase: junit.framework.AssertionFailedError: com.google.android.gms is not installed
从这个堆栈很明显看出在测试device owner相关的测试时找不到了gmscore apk导致的问题,测了一下,发现测试这条确实会复现gmscore被删除的情况,进一步查看log:
07-18 16:53:15.570 14121 14139 D ManagedProvisioning: Deleting package [com.miui.securitycenter] as user 0
07-18 16:53:15.571 14121 14139 D ManagedProvisioning: Deleting package [com.miui.gallery] as user 0
07-18 16:53:15.571 14121 14139 D ManagedProvisioning: Deleting package [com.xiaomi.bttester] as user 0
07-18 16:53:15.571 14121 14139 D ManagedProvisioning: Deleting package [com.xiaomi.market] as user 0
07-18 16:53:15.572 14121 14139 D ManagedProvisioning: Deleting package [com.google.android.gms] as user 0
07-18 16:53:15.572 14121 14139 D ManagedProvisioning: Deleting package [com.android.browser] as user 0
07-18 16:53:15.572 14121 14139 D ManagedProvisioning: Deleting package [com.miui.video] as user 0
07-18 16:53:15.577 14121 14139 D ManagedProvisioning: Deleting package [com.android.mms] as user 0
07-18 16:53:15.578 14121 14139 D ManagedProvisioning: Deleting package [com.android.thememanager] as user 0
07-18 16:53:15.578 14121 14139 D ManagedProvisioning: Deleting package [com.android.camera] as user 0
07-18 16:53:15.579 14121 14139 D ManagedProvisioning: Deleting package [com.miui.bugreport] as user 0
07-18 16:53:15.579 14121 14139 D ManagedProvisioning: Deleting package [com.android.calendar] as user 0
07-18 16:53:15.581 14121 14139 D ManagedProvisioning: Deleting package [com.android.soundrecorder] as user 0
可见测试过程中的确将com.google.android.gms删除了,导致case fail;同时case中断,导致最后测完之后删除了很多pkg
这条case与device owner有关,创建device_owner的过程基本与创建managed profile一致,需要用到ManagedProvisioning.apk这个apk,流程大致如下:
- 启动ProvisioningActivity,调用maybeStartProvisioning
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); mParams = getIntent().getParcelableExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS); initializeUi(mParams); if (savedInstanceState == null || !savedInstanceState.getBoolean(KEY_PROVISIONING_STARTED)) { getProvisioningManager().maybeStartProvisioning(mParams); } } |
- maybeStartProvisioning中创建ProvisioningController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private void startNewProvisioningLocked(final ProvisioningParams params){ ProvisionLogger.logd("Initializing provisioning process"); ... mController = mFactory.createProvisioningController(mContext, params, this); mController.start(mHandlerThread.getLooper()); } /** * This method constructs the controller used for the given type of provisioning. */ @VisibleForTesting public AbstractProvisioningController createProvisioningController( Context context, ProvisioningParams params, ProvisioningControllerCallback callback){ if (mUtils.isDeviceOwnerAction(params.provisioningAction)) { return new DeviceOwnerProvisioningController( context, params, UserHandle.myUserId(), callback); } else { ... } } |
- 其中初始化需要的task,我们需要看的是DeleteNonRequiredAppsTask
1 2 3 4 5 6 7 8 9 10 |
protected void setUpTasks(){ addTasks(new DeviceOwnerInitializeProvisioningTask(mContext, mParams, this)); ... addTasks( new DeleteNonRequiredAppsTask(true /* new profile */, mContext, mParams, this), ... ); } |
- DeleteNonRequiredAppsTask删除不必要的apk
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override public void run(int userId){ Set<String> packagesToDelete = mLogic.getSystemAppsToRemove(userId); ... PackageDeleteObserver packageDeleteObserver = new PackageDeleteObserver(packagesToDelete.size()); for (String packageName : packagesToDelete) { ProvisionLogger.logd("Deleting package [" + packageName + "] as user " + userId); mPm.deletePackageAsUser(packageName, packageDeleteObserver, PackageManager.DELETE_SYSTEM_APP, userId); } } |
- 通过NonRequiredAppsLogic,获取packagesToDelete
1 2 3 4 5 6 7 8 9 |
public Set<String> getSystemAppsToRemove(int userId) { ... Set<String> packagesToDelete = mProvider.getNonRequiredApps(userId); // Retain only new system apps packagesToDelete.retainAll(newSystemApps); return packagesToDelete; } |
6.通过OverlayPackagesProvider提供需要删除的app
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public Set<String> getNonRequiredApps(int userId) { if (mLeaveAllSystemAppsEnabled) { return Collections.emptySet(); } Set<String> nonRequiredApps = getCurrentAppsWithLauncher(userId); // Newly installed system apps are uninstalled when they are not required and are either // disallowed or have a launcher icon. nonRequiredApps.removeAll(getRequiredApps()); // Don't delete the system input method packages in case of Device owner provisioning. if (mProvisioningType == DEVICE_OWNER || mProvisioningType == MANAGED_USER) { nonRequiredApps.removeAll(getSystemInputMethods()); } nonRequiredApps.addAll(getDisallowedApps()); return nonRequiredApps; } |
根据以往经验,gmscore这个package name是需要保留下来的,即在RequiredApps里面
- Required App获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
OverlayPackagesProvider( Context context, ProvisioningParams params, IInputMethodManager iInputMethodManager) { ... switch (params.provisioningAction) { ... case ACTION_PROVISION_MANAGED_DEVICE: case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE: mProvisioningType = DEVICE_OWNER; requiredAppsListArray = R.array.required_apps_managed_device; disallowedAppsListArray = R.array.disallowed_apps_managed_device; vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_device; vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_device; break; ... } Resources resources = context.getResources(); mRequiredAppsList = Arrays.asList(resources.getStringArray(requiredAppsListArray)); mDisallowedAppsList = Arrays.asList(resources.getStringArray(disallowedAppsListArray)); mVendorRequiredAppsList = Arrays.asList( resources.getStringArray(vendorRequiredAppsListArray)); mVendorDisallowedAppsList = Arrays.asList( resources.getStringArray(vendorDisallowedAppsListArray)); } |
可见required apps其实是根据ManagedProvisioning.apk里面相关资源
required_apps_managed_device.xml
vendor_required_apps_managed_device.xml
通过以上的逻辑我们就有了第一个怀疑,是不是因为apk里的相关资源没有配置正确导致的问题。因此下面向着这个猜想的方向进行验证
验证猜想
首先想到的是反编译查看ManagedProvisioning.apk的values下的arrays资源,然而奇怪的是反编译的res文件夹下没有values文件夹。因此我们只能刷机调试,D5X结果:
发现果然没有gms相关包名,为了验证猜想,用E7S的global版本进行验证,结果如下:
通过调试结果验证了猜想,发现是vendor_required_apps_managed_device.xml的值不正确导致的问题;同时,通过观察调试值,很容易想到是vendor_required_apps_managed_device.xml这个属性的值overlay的不正确导致的问题;
即原本应该overlay的属性是vendor/google/products/gms_overlay/packages/apps/ManagedProvisioning/res/values/,但实际上被overlay的属性是miui/config-overlay/v6/global/packages/apps/ManagedProvisioning/res/values/;
那么进一步想到两种修改方案:
1.在实际被overlay的属性中添加gms相关包名
- overlay正确结果
问题解决
以上两种方案,对方案1来说,虽然简单,担不是非常好,因为这是用来overlay miui的相关属性的;但是我们可以先修改这个文件进行测试,添加gms相关包名后,发现case顺利通过,且被删除的app顺利还原;那么放心了,只要overlay正确的属性值就可以顺利解决这个问题;
那么开始调研方案2:
通过在mk文件里加log发现:
首先调用到了miui/build/common_var.mk中的
1 |
MIUI_CONFIG_OVERLAY_ROOT := miui/config-overlay/$(MIUI_VERSION_DIR) |
MIUI_CONFIG_OVERLAY_ROOT的值就是miui/config-overlay/v6
然后在miui/device/common/common.mk中
1 |
MIUI_PRODUCT_PACKAGE_OVERLAYS += $(MIUI_CONFIG_OVERLAY_ROOT)/$(MIUI_COMMON_DIR) |
也就是先overlay了miui/config-overlay/v6/common里面的属性
接下来还会调用miui/device/common/v6/common.mk中的
1 2 3 4 5 6 |
ifeq (true,$(MIUI_HAS_GMSCORE)) $(call inherit-product-if-exists, vendor/google/google_cn/products/common_cn_gms.mk) endif PRODUCT_PACKAGE_OVERLAYS := vendor/google/products/gms_overlay PRODUCT_PACKAGE_OVERLAYS += vendor/google/google_cn/products/gms_overlay |
发现vendor/google/products下面的overlay确实也会被调用,且在miui/config-overlay/v6/common之后,那么按照正常的思路,后面overlay应该会覆盖前面overlay的属性,那么应该不会出错才对
接下来就继续猜测为什么没有overlay成功?
我们会发现,在前面overlay时用的是MIUI_PRODUCT_PACKAGE_OVERLAYS,而后面的是PRODUCT_PACKAGE_OVERLAYS,会不会是这里产生的影响?
查看代码,果然:
1 |
PRODUCT_PACKAGE_OVERLAYS := $(MIUI_PRODUCT_PACKAGE_OVERLAYS) $(PRODUCT_PACKAGE_OVERLAYS) |
如果有MIUI_PRODUCT_PACKAGE_OVERLAYS的overlay,那么这个优先级更高
在common_cn_gms.mk中
PRODUCT_PACKAGE_OVERLAYS := vendor/google/products/gms_overlay
MIUI_PRODUCT_PACKAGE_OVERLAYS += vendor/google/google_cn/products/gms_overlay
并在vendor/google/google_cn下面增加相应需要预置的overlay属性;但是令人惊讶的是测试结果还是fail,说明overlay还是没有成功。
加log打印MIUI_PRODUCT_PACKAGE_OVERLAYS,发现两个路径都在变量 MIUI_PRODUCT_PACKAGE_OVERLAYS中,且vendor/google/google_cn在后面,因此只能怀疑一开始的默认假设: 后面overlay应该会覆盖前面overlay的
则修改miui/device/common /common.mk
1 2 3 4 5 6 7 8 |
ifeq (true,$(PRODUCT_BUILD_INTERNATIONAL)) MIUI_PRODUCT_PACKAGE_OVERLAYS += $(MIUI_CONFIG_OVERLAY_ROOT)/$(MIUI_GLOBAL_DIR) else ifeq (true,$(MIUI_HAS_GMSCORE)) MIUI_PRODUCT_PACKAGE_OVERLAYS += vendor/google/google_cn/products/gms_overlay endif endif MIUI_PRODUCT_PACKAGE_OVERLAYS += $(MIUI_CONFIG_OVERLAY_ROOT)/$(MIUI_COMMON_DIR) |
case pass,因此对于PRODUCT_PACKAGE_OVERLAYS,写在前面的目录优先级高于写在后面目录的优先级
到这里,问题已经得到了解决,这个问题其实不难,但是因为理清了我的一个思维误区,因此觉得有必要记录下来。
问题拓展
为什么资源的overlay是从前往后的,感觉这个不符合逻辑啊,稍稍调研了下。
不管如何,最后要将资源打进apk,最终会使用aapt,那么全局搜了一下,最终发现了相关的mk,build/core/definitions.mk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$(hide) $(AAPT) package $(PRIVATE_AAPT_FLAGS) -m \ $(eval # PRIVATE_PRODUCT_AAPT_CONFIG is intentionally missing-- see comment.) \ $(addprefix -J , $(PRIVATE_SOURCE_INTERMEDIATES_DIR)) \ $(addprefix -M , $(PRIVATE_ANDROID_MANIFEST)) \ $(addprefix -P , $(PRIVATE_RESOURCE_PUBLICS_OUTPUT)) \ $(addprefix -S , $(PRIVATE_RESOURCE_DIR)) \ $(addprefix -A , $(PRIVATE_ASSET_DIR)) \ $(addprefix -I , $(PRIVATE_AAPT_INCLUDES)) \ $(addprefix -G , $(PRIVATE_PROGUARD_OPTIONS_FILE)) \ $(addprefix --min-sdk-version , $(PRIVATE_DEFAULT_APP_TARGET_SDK)) \ $(addprefix --target-sdk-version , $(PRIVATE_DEFAULT_APP_TARGET_SDK)) \ $(if $(filter --version-code,$(PRIVATE_AAPT_FLAGS)),,--version-code $(PLATFORM_SDK_VERSION)) \ $(if $(filter --version-name,$(PRIVATE_AAPT_FLAGS)),,--version-name $(APPS_DEFAULT_VERSION_NAME)) \ $(addprefix --rename-manifest-package , $(PRIVATE_MANIFEST_PACKAGE_NAME)) \ $(addprefix --rename-instrumentation-target-package , $(PRIVATE_MANIFEST_INSTRUMENTATION_FOR)) \ --skip-symbols-without-default-localization endef |
注意这里
1 2 |
$(hide) $(AAPT) package $(PRIVATE_AAPT_FLAGS) -m \ $(addprefix -S , $(PRIVATE_RESOURCE_DIR)) \ |
可见,在dir目录下寻找资源其实用的是aapt -S
那么后面的PRIVATE_RESOURCE_DIR是什么呢?
1 |
$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_RESOURCE_DIR := $(LOCAL_RESOURCE_DIR) |
在build/core/package_internal.mk中
1 2 3 4 5 6 7 8 |
$(wildcard $(foreach dir, $(PRODUCT_PACKAGE_OVERLAYS), \ $(addprefix $(dir)/, $(LOCAL_RESOURCE_DIR)))) \ $(wildcard $(foreach dir, $(DEVICE_PACKAGE_OVERLAYS), \ $(addprefix $(dir)/, $(LOCAL_RESOURCE_DIR))))) ifndef enforce_rro_enabled LOCAL_RESOURCE_DIR := $(package_resource_overlays) $(LOCAL_RESOURCE_DIR) endif |
可见,overlay就是将PRODUCT_PACKAGE_OVERLAYS里面的dir取出,添加到LOCAL_RESOURCE_DIR的前面
最后在packages/apps/ManagedProvisioning目录下执行mma,打出$(LOCAL_RESOURCE_DIR)
1 |
vendor/google/google_cn/products/gms_overlay/packages/apps/ManagedProvisioning/res miui/config-overlay/v6/common/packages/apps/ManagedProvisioning/res vendor/google/google_cn/products/gms_overlay/packages/apps/ManagedProvisioning/res vendor/google/products/gms_overlay/packages/apps/ManagedProvisioning/res vendor/google/google_cn/products/gms_overlay/packages/apps/ManagedProvisioning/res packages/apps/ManagedProvisioning/res frameworks/opt/setupwizard/library//main/res frameworks/opt/setupwizard/library//platform/res |
果然vendor/google/google_cn/products/gms_overlay/packages/apps/ManagedProvisioning/res在miui/config-overlay/v6/common/packages/apps/ManagedProvisioning/res 的前面了
再来查看aapt -S的意思
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[-S resource-sources [-S resource-sources ...]] \\\n" " -S directory in which to find resources. Multiple directories will be scanned\n" " and the first match found (left to right) will take precedence.\n" case 'S': argc--; argv++; if (!argc) { fprintf(stderr, "ERROR: No argument supplied for '-S' option\n"); wantUsage = true; goto bail; } convertPath(argv[0]); bundle.addResourceSourceDir(argv[0]); break; |
aapt -S 取第一个路径进行overlay
问题总结
这个问题其实没什么难度,但是从中理清了overlay顺序的理解误区,因此记录一下;写的比较粗糙。