CTS/GTS問題分析1
問題初探
測試命令: 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相關(guān)的測試時找不到了gmscore apk導(dǎo)致的問題,測了一下,發(fā)現(xiàn)測試這條確實會復(fù)現(xiàn)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刪除了算行,導(dǎo)致case fail涵防;同時case中斷列另,導(dǎo)致最后測完之后刪除了很多pkg
這條case與device owner有關(guān)覆山,創(chuàng)建device_owner的過程基本與創(chuàng)建managed profile一致京革,需要用到ManagedProvisioning.apk這個apk,流程大致如下:
- 啟動ProvisioningActivity辫樱,調(diào)用maybeStartProvisioning
84 @Override
85 protected void onCreate(Bundle savedInstanceState) {
86 super.onCreate(savedInstanceState);
87 mParams = getIntent().getParcelableExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS);
88 initializeUi(mParams);
89
90 if (savedInstanceState == null
91 || !savedInstanceState.getBoolean(KEY_PROVISIONING_STARTED)) {
92 getProvisioningManager().maybeStartProvisioning(mParams);
93 }
94 }
- maybeStartProvisioning中創(chuàng)建ProvisioningController
121 private void startNewProvisioningLocked(final ProvisioningParams params) {
122 ProvisionLogger.logd("Initializing provisioning process");
...
132 mController = mFactory.createProvisioningController(mContext, params, this);
133 mController.start(mHandlerThread.getLooper());
134 }
35 /**
36 * This method constructs the controller used for the given type of provisioning.
37 */
38 @VisibleForTesting
39 public AbstractProvisioningController createProvisioningController(
40 Context context,
41 ProvisioningParams params,
42 ProvisioningControllerCallback callback) {
43 if (mUtils.isDeviceOwnerAction(params.provisioningAction)) {
44 return new DeviceOwnerProvisioningController(
45 context,
46 params,
47 UserHandle.myUserId(),
48 callback);
49 } else {
...
55 }
56 }
- 其中初始化需要的task,我們需要看的是DeleteNonRequiredAppsTask
60 protected void setUpTasks() {
61 addTasks(new DeviceOwnerInitializeProvisioningTask(mContext, mParams, this));
...
74 addTasks(
75 new DeleteNonRequiredAppsTask(true /* new profile */, mContext, mParams, this),
...
78 );
79
83 }
- DeleteNonRequiredAppsTask刪除不必要的apk
73 @Override
74 public void run(int userId) {
75 Set<String> packagesToDelete = mLogic.getSystemAppsToRemove(userId);
...
86 PackageDeleteObserver packageDeleteObserver =
87 new PackageDeleteObserver(packagesToDelete.size());
88 for (String packageName : packagesToDelete) {
89 ProvisionLogger.logd("Deleting package [" + packageName + "] as user " + userId);
90 mPm.deletePackageAsUser(packageName, packageDeleteObserver,
91 PackageManager.DELETE_SYSTEM_APP, userId);
92 }
93 }
- 通過NonRequiredAppsLogic峭拘,獲取packagesToDelete
96 public Set<String> getSystemAppsToRemove(int userId) {
...
110 Set<String> packagesToDelete = mProvider.getNonRequiredApps(userId);
111
112 // Retain only new system apps
113 packagesToDelete.retainAll(newSystemApps);
114
115 return packagesToDelete;
116 }
6.通過OverlayPackagesProvider提供需要刪除的app
139 public Set<String> getNonRequiredApps(int userId) {
140 if (mLeaveAllSystemAppsEnabled) {
141 return Collections.emptySet();
142 }
143
144 Set<String> nonRequiredApps = getCurrentAppsWithLauncher(userId);
145 // Newly installed system apps are uninstalled when they are not required and are either
146 // disallowed or have a launcher icon.
147 nonRequiredApps.removeAll(getRequiredApps());
148 // Don't delete the system input method packages in case of Device owner provisioning.
149 if (mProvisioningType == DEVICE_OWNER || mProvisioningType == MANAGED_USER) {
150 nonRequiredApps.removeAll(getSystemInputMethods());
151 }
152 nonRequiredApps.addAll(getDisallowedApps());
153 return nonRequiredApps;
154 }
根據(jù)以往經(jīng)驗,gmscore這個package name是需要保留下來的狮暑,即在RequiredApps里面
- Required App獲取
73 OverlayPackagesProvider(
74 Context context,
75 ProvisioningParams params,
76 IInputMethodManager iInputMethodManager) {
...
92 switch (params.provisioningAction) {
...
107 case ACTION_PROVISION_MANAGED_DEVICE:
108 case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
109 mProvisioningType = DEVICE_OWNER;
110 requiredAppsListArray = R.array.required_apps_managed_device;
111 disallowedAppsListArray = R.array.disallowed_apps_managed_device;
112 vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_device;
113 vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_device;
114 break;
...
}
120 Resources resources = context.getResources();
121 mRequiredAppsList = Arrays.asList(resources.getStringArray(requiredAppsListArray));
122 mDisallowedAppsList = Arrays.asList(resources.getStringArray(disallowedAppsListArray));
123 mVendorRequiredAppsList = Arrays.asList(
124 resources.getStringArray(vendorRequiredAppsListArray));
125 mVendorDisallowedAppsList = Arrays.asList(
126 resources.getStringArray(vendorDisallowedAppsListArray));
127 }
可見required apps其實是根據(jù)ManagedProvisioning.apk里面相關(guān)資源
required_apps_managed_device.xml
vendor_required_apps_managed_device.xml
通過以上的邏輯我們就有了第一個懷疑鸡挠,是不是因為apk里的相關(guān)資源沒有配置正確導(dǎo)致的問題。因此下面向著這個猜想的方向進行驗證
驗證猜想
首先想到的是反編譯查看ManagedProvisioning.apk的values下的arrays資源搬男,然而奇怪的是反編譯的res文件夾下沒有values文件夾拣展。因此我們只能刷機調(diào)試,D5X結(jié)果:
發(fā)現(xiàn)果然沒有g(shù)ms相關(guān)包名缔逛,為了驗證猜想备埃,用E7S的global版本進行驗證姓惑,結(jié)果如下:
通過調(diào)試結(jié)果驗證了猜想,發(fā)現(xiàn)是vendor_required_apps_managed_device.xml的值不正確導(dǎo)致的問題按脚;同時于毙,通過觀察調(diào)試值,很容易想到是vendor_required_apps_managed_device.xml這個屬性的值overlay的不正確導(dǎo)致的問題辅搬;
即原本應(yīng)該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相關(guān)包名
- overlay正確結(jié)果
問題解決
以上兩種方案堪遂,對方案1來說介蛉,雖然簡單,擔不是非常好溶褪,因為這是用來overlay miui的相關(guān)屬性的币旧;但是我們可以先修改這個文件進行測試,添加gms相關(guān)包名后竿滨,發(fā)現(xiàn)case順利通過佳恬,且被刪除的app順利還原;那么放心了于游,只要overlay正確的屬性值就可以順利解決這個問題;
那么開始調(diào)研方案2:
通過在mk文件里加log發(fā)現(xiàn):
首先調(diào)用到了miui/build/common_var.mk中的
MIUI_CONFIG_OVERLAY_ROOT := miui/config-overlay/$(MIUI_VERSION_DIR)
MIUI_CONFIG_OVERLAY_ROOT的值就是miui/config-overlay/v6
然后在miui/device/common/common.mk中
MIUI_PRODUCT_PACKAGE_OVERLAYS += $(MIUI_CONFIG_OVERLAY_ROOT)/$(MIUI_COMMON_DIR)
也就是先overlay了miui/config-overlay/v6/common里面的屬性
接下來還會調(diào)用miui/device/common/v6/common.mk中的
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
發(fā)現(xiàn)vendor/google/products下面的overlay確實也會被調(diào)用垫言,且在miui/config-overlay/v6/common之后贰剥,那么按照正常的思路,后面overlay應(yīng)該會覆蓋前面overlay的屬性,那么應(yīng)該不會出錯才對
接下來就繼續(xù)猜測為什么沒有overlay成功筷频?
我們會發(fā)現(xiàn)蚌成,在前面overlay時用的是MIUI_PRODUCT_PACKAGE_OVERLAYS,而后面的是PRODUCT_PACKAGE_OVERLAYS凛捏,會不會是這里產(chǎn)生的影響?
查看代碼担忧,果然:
PRODUCT_PACKAGE_OVERLAYS := $(MIUI_PRODUCT_PACKAGE_OVERLAYS) $(PRODUCT_PACKAGE_OVERLAYS)
如果有MIUI_PRODUCT_PACKAGE_OVERLAYS的overlay,那么這個優(yōu)先級更高
在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下面增加相應(yīng)需要預(yù)置的overlay屬性坯癣;但是令人驚訝的是測試結(jié)果還是fail瓶盛,說明overlay還是沒有成功。
加log打印MIUI_PRODUCT_PACKAGE_OVERLAYS示罗,發(fā)現(xiàn)兩個路徑都在變量 MIUI_PRODUCT_PACKAGE_OVERLAYS中惩猫,且vendor/google/google_cn在后面,因此只能懷疑一開始的默認假設(shè): 后面overlay應(yīng)該會覆蓋前面overlay的
則修改miui/device/common /common.mk
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轧房,寫在前面的目錄優(yōu)先級高于寫在后面目錄的優(yōu)先級
到這里,問題已經(jīng)得到了解決绍绘,這個問題其實不難奶镶,但是因為理清了我的一個思維誤區(qū)迟赃,因此覺得有必要記錄下來。
問題拓展
為什么資源的overlay是從前往后的厂镇,感覺這個不符合邏輯啊纤壁,稍稍調(diào)研了下。
不管如何剪撬,最后要將資源打進apk摄乒,最終會使用aapt,那么全局搜了一下残黑,最終發(fā)現(xiàn)了相關(guān)的mk馍佑,build/core/definitions.mk
2027$(hide) $(AAPT) package $(PRIVATE_AAPT_FLAGS) -m \
2028 $(eval # PRIVATE_PRODUCT_AAPT_CONFIG is intentionally missing-- see comment.) \
2029 $(addprefix -J , $(PRIVATE_SOURCE_INTERMEDIATES_DIR)) \
2030 $(addprefix -M , $(PRIVATE_ANDROID_MANIFEST)) \
2031 $(addprefix -P , $(PRIVATE_RESOURCE_PUBLICS_OUTPUT)) \
2032 $(addprefix -S , $(PRIVATE_RESOURCE_DIR)) \
2033 $(addprefix -A , $(PRIVATE_ASSET_DIR)) \
2034 $(addprefix -I , $(PRIVATE_AAPT_INCLUDES)) \
2035 $(addprefix -G , $(PRIVATE_PROGUARD_OPTIONS_FILE)) \
2036 $(addprefix --min-sdk-version , $(PRIVATE_DEFAULT_APP_TARGET_SDK)) \
2037 $(addprefix --target-sdk-version , $(PRIVATE_DEFAULT_APP_TARGET_SDK)) \
2038 $(if $(filter --version-code,$(PRIVATE_AAPT_FLAGS)),,--version-code $(PLATFORM_SDK_VERSION)) \
2039 $(if $(filter --version-name,$(PRIVATE_AAPT_FLAGS)),,--version-name $(APPS_DEFAULT_VERSION_NAME)) \
2040 $(addprefix --rename-manifest-package , $(PRIVATE_MANIFEST_PACKAGE_NAME)) \
2041 $(addprefix --rename-instrumentation-target-package , $(PRIVATE_MANIFEST_INSTRUMENTATION_FOR)) \
2042 --skip-symbols-without-default-localization
2043endef
注意這里
$(hide) $(AAPT) package $(PRIVATE_AAPT_FLAGS) -m \
$(addprefix -S , $(PRIVATE_RESOURCE_DIR)) \
可見,在dir目錄下尋找資源其實用的是aapt -S
那么后面的PRIVATE_RESOURCE_DIR是什么呢梨水?
$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_RESOURCE_DIR := $(LOCAL_RESOURCE_DIR)
在build/core/package_internal.mk中
94 $(wildcard $(foreach dir, $(PRODUCT_PACKAGE_OVERLAYS), \
95 $(addprefix $(dir)/, $(LOCAL_RESOURCE_DIR)))) \
96 $(wildcard $(foreach dir, $(DEVICE_PACKAGE_OVERLAYS), \
97 $(addprefix $(dir)/, $(LOCAL_RESOURCE_DIR)))))
124ifndef enforce_rro_enabled
125 LOCAL_RESOURCE_DIR := $(package_resource_overlays) $(LOCAL_RESOURCE_DIR)
126endif
可見拭荤,overlay就是將PRODUCT_PACKAGE_OVERLAYS里面的dir取出,添加到LOCAL_RESOURCE_DIR的前面
最后在packages/apps/ManagedProvisioning目錄下執(zhí)行mma疫诽,打出$(LOCAL_RESOURCE_DIR)
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的意思
[-S resource-sources [-S resource-sources ...]] \\\n"
130 " -S directory in which to find resources. Multiple directories will be scanned\n"
131 " and the first match found (left to right) will take precedence.\n"
467 case 'S':
468 argc--;
469 argv++;
470 if (!argc) {
471 fprintf(stderr, "ERROR: No argument supplied for '-S' option\n");
472 wantUsage = true;
473 goto bail;
474 }
475 convertPath(argv[0]);
476 bundle.addResourceSourceDir(argv[0]);
477 break;
aapt -S 取第一個路徑進行overlay
問題總結(jié)
這個問題其實沒什么難度舅世,但是從中理清了overlay順序的理解誤區(qū),因此記錄一下奇徒;寫的比較粗糙雏亚。