CTS/GTS問題分析5
問題初探
測試命令:
run cts -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedManagedProfileOwnerTestApi25#testResetPasswordFbe
錯誤有兩種情況,一種是直接進(jìn)入系統(tǒng)桌面,一種是起一個測試case中的空白activity,經(jīng)過分析,兩者都是同一個原因造成的佩耳。因此以任一種情況舉例。報錯堆棧如下:
08-10 19:46:58.992 1471 2291 E ActivityManager: Failure starting process com.android.cts.launcherapps.simpleapp
08-10 19:46:58.992 1471 2291 E ActivityManager: java.lang.SecurityException: Package com.android.cts.launcherapps.simpleapp is not encryption aware!
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.pm.PackageManagerService.checkPackageStartable(PackageManagerService.java:3789)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:3927)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:3885)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:3756)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityStackSupervisor.startSpecificActivityLocked(ActivityStackSupervisor.java:1647)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityStack.makeVisibleAndRestartIfNeeded(ActivityStack.java:2108)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityStack.ensureActivitiesVisibleLocked(ActivityStack.java:1921)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityStackSupervisor.ensureActivitiesVisibleLocked(ActivityStackSupervisor.java:3632)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityStackSupervisor.attachApplicationLocked(ActivityStackSupervisor.java:1014)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:7281)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:7348)
08-10 19:46:58.992 1471 2291 E ActivityManager: at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:292)
08-10 19:46:58.992 1471 2291 E ActivityManager: at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3035)
08-10 19:46:58.992 1471 2291 E ActivityManager: at android.os.Binder.execTransact(Binder.java:679)
08-10 19:46:58.994 1471 2291 W ActivityManager: Skipping resume of top activity ActivityRecord{49151aa u10 com.android.cts.launcherapps.simpleapp/.SimpleActivity t1000001}: user 10 is stopped
上面報錯的原因是,當(dāng)手機(jī)重啟時啟動了測試界面拧咳,但是該測試apk是沒有directBoot屬性的,因此當(dāng)沒有解鎖時就會報上面的錯誤荧嵌,測試過程中發(fā)現(xiàn)確實沒有類似鎖屏的頁面呛踊,因此case fail,那么下面就要從鎖屏界面沒有起來的原因進(jìn)行分析
問題分析
經(jīng)過與鎖屏同事的討論啦撮,發(fā)現(xiàn)鎖屏界面沒有起來的原因如下:
本來應(yīng)該調(diào)起啟用鎖屏的地方
private Intent interceptWithConfirmCredentialsIfNeeded(Intent intent, String resolvedType,
ActivityInfo aInfo, String callingPackage, int userId) {
if (!mService.mUserController.shouldConfirmCredentials(userId)) {
//出現(xiàn)異常的時候谭网,此處返回了
return null;
}
// TODO(b/28935539): should allow certain activities to bypass work challenge
final IIntentSender target = mService.getIntentSenderLocked(
INTENT_SENDER_ACTIVITY, callingPackage,
Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
new String[]{ resolvedType },
FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE, null);
final KeyguardManager km = (KeyguardManager) mService.mContext
.getSystemService(KEYGUARD_SERVICE);
//此處啟動確認(rèn)密碼界面
final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
if (newIntent == null) {
return null;
}
newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
FLAG_ACTIVITY_TASK_ON_HOME);
newIntent.putExtra(EXTRA_PACKAGE_NAME, aInfo.packageName);
newIntent.putExtra(EXTRA_INTENT, new IntentSender(target));
return newIntent;
}
繼續(xù)往下看shouldConfirmCredentials這個方法,
if (mStartedUsers.get(userId) == null) {
//出現(xiàn)異常時在此處返回
return false;
}
mStartedUsers的賦值是在startUser里面賦值的赃春,該方法在手機(jī)reboot的時候會被startProfilesLocked調(diào)用
void startProfilesLocked() {
if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
mCurrentUserId, false /* enabledOnly */);
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
//出現(xiàn)異常的時候愉择,第一個條件返回了false,說明user沒有初始化
if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
&& user.id != mCurrentUserId && !user.isQuietModeEnabled()) {
//這里沒有被調(diào)用
profilesToStart.add(user);
}
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
if (i < profilesToStartSize) {
Slog.w(TAG, "More profiles than MAX_RUNNING_USERS");
}
}
startProfilesLocked方法如上织中,出現(xiàn)異常的時候(user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED這個條件返回了false锥涕,從而導(dǎo)致profilesToStart的size為0 , start user沒有正常執(zhí)行狭吼,說明user沒有正常初始化层坠。
還有一個地方也能證明沒有初始化完畢,手機(jī)重啟后刁笙,執(zhí)行adb shell dumpsys users破花,發(fā)現(xiàn)被創(chuàng)建的user,其state始終為-1疲吸,即始終沒有調(diào)用 setUserState座每,從log里也證實了這一點
user的初始化是在UserManagerService.java的makeInitialized(int userId)方法里面
public void makeInitialized(int userId) {
checkManageUsersPermission("makeInitialized");
boolean scheduleWriteUser = false;
UserData userData;
synchronized (mUsersLock) {
userData = mUsers.get(userId);
if (userData == null || userData.info.partial) {
return;
}
if ((userData.info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
userData.info.flags |= UserInfo.FLAG_INITIALIZED;
scheduleWriteUser = true;
}
}
if (scheduleWriteUser) {
scheduleWriteUser(userData);
}
}
該方法也正常最后一行要求寫入user數(shù)據(jù),問題就出現(xiàn)在scheduleWriteUser這個方法里面
private void scheduleWriteUser(UserData UserData) {
if (DBG) {
debug("scheduleWriteUser");
}
// No need to wrap it within a lock -- worst case, we'll just post the same message
// twice.
if (!mHandler.hasMessages(WRITE_USER_MSG, UserData)) {
Message msg = mHandler.obtainMessage(WRITE_USER_MSG, UserData);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
}
}
從代碼上看摘悴,scheduleWriteUser方法會通過handler發(fā)送一個message峭梳,handler接收到消息之后才會寫入user數(shù)據(jù)。這個message有WRITE_USER_DELAY = 2*1000的dalay蹂喻,出現(xiàn)問題的時候葱椭,message發(fā)送出去之后捂寿,handler還沒接收到消息,系統(tǒng)就reboot了挫以,從而導(dǎo)致寫入數(shù)據(jù)失敗者蠕。到了這里基本可以確定case寫的不夠完美。下面進(jìn)行case的修改
測試case修改
首先先確認(rèn)我們的分析是否正確掐松,在case進(jìn)入重啟階段之前踱侣,先sleep 2s已保證所創(chuàng)建的User有足夠的時間完成初始化操作并將UserInfo中flag的值加上FLAG_INITIALIZED,并加上log大磺,發(fā)現(xiàn):
08-30 04:25:48.120 1423 2748 I weijuncheng: make user 11 flag FLAG_INITIALIZED
08-30 04:25:48.120 1423 2748 I weijuncheng: start to write user11
08-30 04:25:48.120 1423 2748 I weijuncheng: send message WRITE_USER_MSG
08-30 04:25:50.120 1423 1423 I weijuncheng: userFile = /data/system/users/11.xml
08-30 04:25:50.121 1423 1423 I weijuncheng: real start writeUserLP
2s后順利將userdata(flag已經(jīng)置為FLAG_INITIALIZED)寫到/data/system/users/11.xml文件里
對比沒加之前
08-30 04:15:13.750 7889 8987 I weijuncheng: make user 10 flag FLAG_INITIALIZED
08-30 04:15:13.750 7889 8987 I weijuncheng: start to write user10
08-30 04:15:13.750 7889 8987 I weijuncheng: send message WRITE_USER_MSG
發(fā)送只是將message傳出去了抡句,但是還沒有來的寫就自動重啟了,這里我們確認(rèn)了case確實有問題杠愧,那么該如何修復(fù)呢待榔。
首先這個case是寫在jar包里的,也就是host端流济,沒有context锐锣,我們沒有辦法得到UserManagerService的代理來進(jìn)行判斷,那么能想到的就是通過shell命令來判斷device設(shè)備的狀態(tài)绳瘟,google給我們寫好了接口雕憔,如下:
我們可以用到其中的:
989 @Override
990 public int getUserFlags(int userId) throws DeviceNotAvailableException {
991 checkApiLevelAgainst("getUserFlags", 22);
992 final String commandOutput = executeShellCommand("pm list users");
993 Matcher matcher = findUserInfo(commandOutput);
994 while(matcher.find()) {
995 if (Integer.parseInt(matcher.group(2)) == userId) {
996 return Integer.parseInt(matcher.group(6), 16);
997 }
998 }
999 CLog.w("Could not find any flags for userId: %d in output: %s", userId, commandOutput);
1000 return INVALID_USER_ID;
1001 }
980 private Matcher findUserInfo(String pmListUsersOutput) {
981 Pattern pattern = Pattern.compile(USER_PATTERN);
982 Matcher matcher = pattern.matcher(pmListUsersOutput);
983 return matcher;
984 }
/** user pattern in the output of "pm list users" = TEXT{<id>:<name>:<flags>} TEXT * */
注意pm list users的各項含義,這樣就可以得到相應(yīng)user的flag糖声,那么等到創(chuàng)建的user的flag被置為FLAG_INITIALIZED之后再重啟即可
我提的修復(fù): https://android-review.googlesource.com/c/platform/cts/+/734030
private boolean isUserInitialized(int mUserId) throws Exception{
return (getUserFlags(mUserId) & FLAG_INITIALIZED) == FLAG_INITIALIZED;
}
private void waitForUserInitialized(int mUserId) throws Exception {
for (int i = 0; i < 3; i++) {
if (isUserInitialized(mUserId)) {
Log.d(TAG, "Yay, created user "+mUserId+" is initialized!");
return;
}
Log.d(TAG, "Waiting for created user being initialized...");
Thread.sleep(1000);
}
throw new AssertionError("Created user failed to become initialized!");
}
public void testResetPasswordFbe() throws Exception {
if (!mHasFeature || !mSupportsFbe) {
return;
}
//add here to wait until managedprofile user being initialized
waitForUserInitialized(mUserId);
// Lock FBE and verify resetPassword is disabled
executeDeviceTestMethod(FBE_HELPER_CLASS, "testSetPassword");
rebootAndWaitUntilReady();
executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordDisabled");
// Start an activity in managed profile to trigger work challenge
startSimpleActivityAsUser(mUserId);
// Unlock FBE and verify resetPassword is enabled again
executeDeviceTestMethod(FBE_HELPER_CLASS, "testUnlockFbe");
executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordManagedProfile");
}
但是代碼提交上去了才發(fā)現(xiàn)google也修復(fù)了斤彼,https://android-review.googlesource.com/c/platform/cts/+/740245,而且修的很簡單
@Override
public void testResetPasswordFbe() throws Exception {
if (!mHasFeature || !mSupportsFbe) {
return;
}
// Make sure user initialization is complete before proceeding.
waitForBroadcastIdle();
// Lock FBE and verify resetPassword is disabled
executeDeviceTestMethod(FBE_HELPER_CLASS, "testSetPassword");
rebootAndWaitUntilReady();
executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordDisabled");
// Start an activity in managed profile to trigger work challenge
startSimpleActivityAsUser(mUserId);
// Unlock FBE and verify resetPassword is enabled again
executeDeviceTestMethod(FBE_HELPER_CLASS, "testUnlockFbe");
executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordManagedProfile");
}
只加了一行就達(dá)到了同樣的效果蘸泻,waitForBroadcastIdle()
25042 public void waitForBroadcastIdle(PrintWriter pw) {
25043 enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
25044 while (true) {
25045 boolean idle = true;
25046 synchronized (this) {
25047 for (BroadcastQueue queue : mBroadcastQueues) {
25048 if (!queue.isIdle()) {
25049 final String msg = "Waiting for queue " + queue + " to become idle...";
25050 pw.println(msg);
25051 pw.flush();
25052 Slog.v(TAG, msg);
25053 idle = false;
25054 }
25055 }
25056 }
25057
25058 if (idle) {
25059 final String msg = "All broadcast queues are idle!";
25060 pw.println(msg);
25061 pw.flush();
25062 Slog.v(TAG, msg);
25063 return;
25064 } else {
25065 SystemClock.sleep(1000);
25066 }
25067 }
25068 }
很聰明的實現(xiàn)方法
問題總結(jié)
了解了host端CTS case也有很多可用接口琉苇,同時提case前先看看google是否已經(jīng)修復(fù)