CTS/GTS问题分析5
问题初探
测试命令:
run cts -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedManagedProfileOwnerTestApi25#testResetPasswordFbe
错误有两种情况,一种是直接进入系统桌面,一种是起一个测试case中的空白activity,经过分析,两者都是同一个原因造成的。因此以任一种情况举例。报错堆栈如下:
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,那么下面就要从锁屏界面没有起来的原因进行分析
问题分析
经过与锁屏同事的讨论,发现锁屏界面没有起来的原因如下:
本来应该调起启用锁屏的地方
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这个方法,
if (mStartedUsers.get(userId) == null) {
//出现异常时在此处返回
return false;
}
mStartedUsers的赋值是在startUser里面赋值的,该方法在手机reboot的时候会被startProfilesLocked调用
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)方法里面
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这个方法里面
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,发现:
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文件里
对比没加之前
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给我们写好了接口,如下:
我们可以用到其中的:
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
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,而且修的很简单
@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()
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是否已经修复