From e5a06ca619fb37bc20e5b8d38ab8f2c19f9257cb Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Wed, 1 Feb 2023 19:38:08 -0800 Subject: HsumBootUserInitializer creates MainUser earlier Previously, on first boot, an HSUM device would unlock the system user, then create the MainUser (if needed), and then switch to the boot user, all at around the same time. This cl reorders these events and spreads them out: * first, early on (just prior to PHASE_SYSTEM_SERVICES_READY), the MainUser is created (if needed) * later (after PHASE_THIRD_PARTY_APPS_CAN_START), the system user is unlocked * then we promptly switch to the boot user. This needs to be after PHASE_THIRD_PARTY_APPS_CAN_START since special apps may designate a different boot user. This will ensure that the MainUser can be retrieved much earlier on by system services whose operations may depend on it. Also, the structure of BootUserInitializer is renamed to better reflect its role, and its structure is modified slightly to allow SystemServer to use it more easily. Bug: 262438904 Bug: 266098768 Test: flashed HSUM and confirmed that MainUser was created as needed Test: atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest Change-Id: Ic84b1be538234b2bfde3ae792c9a923ace55f24c --- .../com/android/server/BootUserInitializer.java | 128 -------------- .../android/server/HsumBootUserInitializer.java | 190 +++++++++++++++++++++ services/java/com/android/server/SystemServer.java | 21 ++- 3 files changed, 206 insertions(+), 133 deletions(-) delete mode 100644 services/java/com/android/server/BootUserInitializer.java create mode 100644 services/java/com/android/server/HsumBootUserInitializer.java (limited to 'services/java') diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java deleted file mode 100644 index 3d71739924f7..000000000000 --- a/services/java/com/android/server/BootUserInitializer.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server; - -import android.annotation.UserIdInt; -import android.content.ContentResolver; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; - -import com.android.server.am.ActivityManagerService; -import com.android.server.pm.UserManagerInternal; -import com.android.server.utils.Slogf; -import com.android.server.utils.TimingsTraceAndSlog; - -/** - * Class responsible for booting the device in the proper user on headless system user mode. - * - */ -// TODO(b/204091126): STOPSHIP - provide proper APIs -final class BootUserInitializer { - - private static final String TAG = BootUserInitializer.class.getSimpleName(); - - // TODO(b/204091126): STOPSHIP - set to false or dynamic value - private static final boolean DEBUG = true; - - private final ActivityManagerService mAms; - private final ContentResolver mContentResolver; - - BootUserInitializer(ActivityManagerService am, ContentResolver contentResolver) { - mAms = am; - mContentResolver = contentResolver; - } - - public void init(TimingsTraceAndSlog t) { - Slogf.i(TAG, "init())"); - - // TODO(b/204091126): in the long term, we need to decide who's reponsible for that, - // this class or the setup wizard app - provisionHeadlessSystemUser(); - - unlockSystemUser(t); - - try { - t.traceBegin("getBootUser"); - int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser(); - t.traceEnd(); - t.traceBegin("switchToBootUser-" + bootUser); - switchToBootUser(bootUser); - t.traceEnd(); - } catch (UserManager.CheckedUserOperationException e) { - Slogf.wtf(TAG, "Failed to created boot user", e); - } - } - - /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */ - private void provisionHeadlessSystemUser() { - if (isDeviceProvisioned()) { - Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned"); - return; - } - - Slogf.i(TAG, "Marking USER_SETUP_COMPLETE for system user"); - Settings.Secure.putInt(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1); - Slogf.i(TAG, "Marking DEVICE_PROVISIONED for system user"); - Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 1); - } - - private boolean isDeviceProvisioned() { - try { - return Settings.Global.getInt(mContentResolver, - Settings.Global.DEVICE_PROVISIONED) == 1; - } catch (Exception e) { - Slogf.wtf(TAG, "DEVICE_PROVISIONED setting not found.", e); - return false; - } - } - - // NOTE: Mostly copied from Automotive's InitialUserSetter - private void unlockSystemUser(TimingsTraceAndSlog t) { - Slogf.i(TAG, "Unlocking system user"); - t.traceBegin("unlock-system-user"); - try { - // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not - // update the state and USER_SYSTEM unlock happens twice. - t.traceBegin("am.startUser"); - boolean started = mAms.startUserInBackgroundWithListener(UserHandle.USER_SYSTEM, - /* listener= */ null); - t.traceEnd(); - if (!started) { - Slogf.w(TAG, "could not restart system user in background; trying unlock instead"); - t.traceBegin("am.unlockUser"); - boolean unlocked = mAms.unlockUser(UserHandle.USER_SYSTEM, /* token= */ null, - /* secret= */ null, /* listener= */ null); - t.traceEnd(); - if (!unlocked) { - Slogf.w(TAG, "could not unlock system user either"); - return; - } - } - } finally { - t.traceEnd(); - } - } - - private void switchToBootUser(@UserIdInt int bootUserId) { - Slogf.i(TAG, "Switching to boot user %d", bootUserId); - boolean started = mAms.startUserInForegroundWithListener(bootUserId, - /* unlockListener= */ null); - if (!started) { - Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId); - } - } -} diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java new file mode 100644 index 000000000000..cc6c36ef1ed5 --- /dev/null +++ b/services/java/com/android/server/HsumBootUserInitializer.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server; + +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ContentResolver; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; + +import com.android.server.am.ActivityManagerService; +import com.android.server.pm.UserManagerInternal; +import com.android.server.utils.Slogf; +import com.android.server.utils.TimingsTraceAndSlog; + +/** + * Class responsible for booting the device in the proper user on headless system user mode. + * + */ +final class HsumBootUserInitializer { + + private static final String TAG = HsumBootUserInitializer.class.getSimpleName(); + + private final UserManagerInternal mUmi; + private final ActivityManagerService mAms; + private final ContentResolver mContentResolver; + + /** Whether this device should always have a non-removable MainUser, including at first boot. */ + private final boolean mShouldAlwaysHaveMainUser; + + /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */ + public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am, + ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) { + + if (!UserManager.isHeadlessSystemUserMode()) { + return null; + } + return new HsumBootUserInitializer( + LocalServices.getService(UserManagerInternal.class), + am, contentResolver, shouldAlwaysHaveMainUser); + } + + private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am, + ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) { + mUmi = umi; + mAms = am; + mContentResolver = contentResolver; + this.mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser; + } + + /** + * Initialize this object, and create MainUser if needed. + * + * Should be called before PHASE_SYSTEM_SERVICES_READY as services' setups may require MainUser, + * but probably after PHASE_LOCK_SETTINGS_READY since that may be needed for user creation. + */ + public void init(TimingsTraceAndSlog t) { + Slogf.i(TAG, "init())"); + + // TODO(b/204091126): in the long term, we need to decide who's reponsible for that, + // this class or the setup wizard app + provisionHeadlessSystemUser(); + + if (mShouldAlwaysHaveMainUser) { + t.traceBegin("createMainUserIfNeeded"); + createMainUserIfNeeded(); + t.traceEnd(); + } + } + + private void createMainUserIfNeeded() { + int mainUser = mUmi.getMainUserId(); + if (mainUser != UserHandle.USER_NULL) { + Slogf.d(TAG, "Found existing MainUser, userId=%d", mainUser); + return; + } + + Slogf.d(TAG, "Creating a new MainUser"); + try { + final UserInfo newInitialUser = mUmi.createUserEvenWhenDisallowed( + /* name= */ null, // null will appear as "Owner" in on-demand localisation + UserManager.USER_TYPE_FULL_SECONDARY, + UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN, + /* disallowedPackages= */ null, + /* token= */ null); + if (newInitialUser == null) { + Slogf.wtf(TAG, "Initial bootable MainUser creation failed: returned null"); + } else { + Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id); + } + } catch (UserManager.CheckedUserOperationException e) { + Slogf.wtf(TAG, "Initial bootable MainUser creation failed", e); + } + } + + /** + * Put the device into the correct user state: unlock the system and switch to the boot user. + * + * Should only call once PHASE_THIRD_PARTY_APPS_CAN_START is reached to ensure that privileged + * apps have had the chance to set the boot user, if applicable. + */ + public void systemRunning(TimingsTraceAndSlog t) { + unlockSystemUser(t); + + try { + t.traceBegin("getBootUser"); + final int bootUser = mUmi.getBootUser(); + t.traceEnd(); + t.traceBegin("switchToBootUser-" + bootUser); + switchToBootUser(bootUser); + t.traceEnd(); + } catch (UserManager.CheckedUserOperationException e) { + Slogf.wtf(TAG, "Failed to switch to boot user since there isn't one."); + } + } + + /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */ + private void provisionHeadlessSystemUser() { + if (isDeviceProvisioned()) { + Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned"); + return; + } + + Slogf.i(TAG, "Marking USER_SETUP_COMPLETE for system user"); + Settings.Secure.putInt(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1); + Slogf.i(TAG, "Marking DEVICE_PROVISIONED for system user"); + Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 1); + } + + private boolean isDeviceProvisioned() { + try { + return Settings.Global.getInt(mContentResolver, + Settings.Global.DEVICE_PROVISIONED) == 1; + } catch (Exception e) { + Slogf.wtf(TAG, "DEVICE_PROVISIONED setting not found.", e); + return false; + } + } + + // NOTE: Mostly copied from Automotive's InitialUserSetter + private void unlockSystemUser(TimingsTraceAndSlog t) { + Slogf.i(TAG, "Unlocking system user"); + t.traceBegin("unlock-system-user"); + try { + // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not + // update the state and USER_SYSTEM unlock happens twice. + t.traceBegin("am.startUser"); + boolean started = mAms.startUserInBackgroundWithListener(UserHandle.USER_SYSTEM, + /* listener= */ null); + t.traceEnd(); + if (!started) { + Slogf.w(TAG, "could not restart system user in background; trying unlock instead"); + t.traceBegin("am.unlockUser"); + boolean unlocked = mAms.unlockUser(UserHandle.USER_SYSTEM, /* token= */ null, + /* secret= */ null, /* listener= */ null); + t.traceEnd(); + if (!unlocked) { + Slogf.w(TAG, "could not unlock system user either"); + return; + } + } + } finally { + t.traceEnd(); + } + } + + private void switchToBootUser(@UserIdInt int bootUserId) { + Slogf.i(TAG, "Switching to boot user %d", bootUserId); + boolean started = mAms.startUserInForegroundWithListener(bootUserId, + /* unlockListener= */ null); + if (!started) { + Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a15c6d288cfd..d22be9ec01d0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -75,7 +75,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.UserManager; import android.os.storage.IStorageManager; import android.provider.DeviceConfig; import android.provider.Settings; @@ -2694,6 +2693,18 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startBootPhase(t, SystemService.PHASE_LOCK_SETTINGS_READY); t.traceEnd(); + // Create initial user if needed, which should be done early since some system services rely + // on it in their setup, but likely needs to be done after LockSettingsService is ready. + final HsumBootUserInitializer hsumBootUserInitializer = + HsumBootUserInitializer.createInstance( + mActivityManagerService, mContentResolver, + context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin)); + if (hsumBootUserInitializer != null) { + t.traceBegin("HsumBootUserInitializer.init"); + hsumBootUserInitializer.init(t); + t.traceEnd(); + } + t.traceBegin("StartBootPhaseSystemServicesReady"); mSystemServiceManager.startBootPhase(t, SystemService.PHASE_SYSTEM_SERVICES_READY); t.traceEnd(); @@ -2961,10 +2972,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startBootPhase(t, SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); t.traceEnd(); - if (UserManager.isHeadlessSystemUserMode() && !isAutomotive) { - // TODO(b/204091126): remove isAutomotive check once the workflow is finalized - t.traceBegin("BootUserInitializer"); - new BootUserInitializer(mActivityManagerService, mContentResolver).init(t); + if (hsumBootUserInitializer != null && !isAutomotive) { + // TODO(b/261924826): remove isAutomotive check once the workflow is finalized + t.traceBegin("HsumBootUserInitializer.systemRunning"); + hsumBootUserInitializer.systemRunning(t); t.traceEnd(); } -- cgit v1.2.3-59-g8ed1b