问题初探
测试命令:
run cts -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedManagedProfileOwnerTestApi25#testResetPasswordFbe
错误有两种情况,一种是直接进入系统桌面,一种是起一个测试case中的空白activity,经过分析,两者都是同一个原因造成的。因此以任一种情况举例。报错堆栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 |
上面报错的原因是,当手机重启时启动了测试界面,但是该测试apk是没有directBoot属性的,因此当没有解锁时就会报上面的错误,测试过程中发现确实没有类似锁屏的页面,因此case fail,那么下面就要从锁屏界面没有起来的原因进行分析
问题分析
经过与锁屏同事的讨论,发现锁屏界面没有起来的原因如下:
本来应该调起启用锁屏的地方
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 Intent interceptWithConfirmCredentialsIfNeeded(Intent intent, String resolvedType, ActivityInfo aInfo, String callingPackage, int userId){ if (!mService.mUserController.shouldConfirmCredentials(userId)) { //出现异常的时候,此处返回了 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); //此处启动确认密码界面 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; } |
继续往下看shouldConfirmCredentials这个方法,
1 2 3 4 |
if (mStartedUsers.get(userId) == null) { //出现异常时在此处返回 return false; } |
mStartedUsers的赋值是在startUser里面赋值的,该方法在手机reboot的时候会被startProfilesLocked调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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) { //出现异常的时候,第一个条件返回了false,说明user没有初始化 if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED && user.id != mCurrentUserId && !user.isQuietModeEnabled()) { //这里没有被调用 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方法如上,出现异常的时候(user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED这个条件返回了false,从而导致profilesToStart的size为0 , start user没有正常执行,说明user没有正常初始化。
还有一个地方也能证明没有初始化完毕,手机重启后,执行adb shell dumpsys users,发现被创建的user,其state始终为-1,即始终没有调用 setUserState,从log里也证实了这一点
user的初始化是在UserManagerService.java的makeInitialized(int userId)方法里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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数据,问题就出现在scheduleWriteUser这个方法里面
1 2 3 4 5 6 7 8 9 10 11 |
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发送一个message,handler接收到消息之后才会写入user数据。这个message有WRITE_USER_DELAY = 2*1000的dalay,出现问题的时候,message发送出去之后,handler还没接收到消息,系统就reboot了,从而导致写入数据失败。到了这里基本可以确定case写的不够完美。下面进行case的修改
测试case修改
首先先确认我们的分析是否正确,在case进入重启阶段之前,先sleep 2s已保证所创建的User有足够的时间完成初始化操作并将UserInfo中flag的值加上FLAG_INITIALIZED,并加上log,发现:
1 2 3 4 5 |
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已经置为FLAG_INITIALIZED)写到/data/system/users/11.xml文件里
对比没加之前
1 2 3 |
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 |
发送只是将message传出去了,但是还没有来的写就自动重启了,这里我们确认了case确实有问题,那么该如何修复呢。
首先这个case是写在jar包里的,也就是host端,没有context,我们没有办法得到UserManagerService的代理来进行判断,那么能想到的就是通过shell命令来判断device设备的状态,google给我们写好了接口,如下:
我们可以用到其中的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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的各项含义,这样就可以得到相应user的flag,那么等到创建的user的flag被置为FLAG_INITIALIZED之后再重启即可
我提的修复: https://android-review.googlesource.com/c/platform/cts/+/734030
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 26 27 28 29 30 31 32 33 34 35 36 37 |
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"); } |
但是代码提交上去了才发现google也修复了,https://android-review.googlesource.com/c/platform/cts/+/740245,而且修的很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@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"); } |
只加了一行就达到了同样的效果,waitForBroadcastIdle()
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 26 27 |
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 } |
很聪明的实现方法
问题总结
了解了host端CTS case也有很多可用接口,同时提case前先看看google是否已经修复