Initial implementation of system service optimizations for different type of users.
On R, we want to optimize boot time by not starting system services for some types of users,
like a headless system user (which is the case for Automotive)
As a "guinea pig", it optimizes VoiceInteractionService for headless system user, so the 3rd-party app
service is not bound for user 0 (and hence its process is not launched).
This change improves boot time on Automotive in about 100ms.
Test: atest CtsVoiceInteractionTestCases CtsAssistTestCases # on walleye and Automotive
Test: manual verification on logcat
Bug: 133242016
Fixes: 137878080
Change-Id: Ib0a902855e32691a1d00bfa77ee82c8e2430977c
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 1c37c64..fcdc81c 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -16,11 +16,17 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.annotation.UnsupportedAppUsage;
+import android.annotation.UserIdInt;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.DebugUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Per-user information.
@@ -119,10 +125,31 @@
*/
public static final int FLAG_SYSTEM = 0x00000800;
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_PRIMARY,
+ FLAG_ADMIN,
+ FLAG_GUEST,
+ FLAG_RESTRICTED,
+ FLAG_INITIALIZED,
+ FLAG_MANAGED_PROFILE,
+ FLAG_DISABLED,
+ FLAG_QUIET_MODE,
+ FLAG_EPHEMERAL,
+ FLAG_DEMO,
+ FLAG_FULL,
+ FLAG_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserInfoFlag {
+ }
+
public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
@UnsupportedAppUsage
- public int id;
+ public @UserIdInt int id;
@UnsupportedAppUsage
public int serialNumber;
@UnsupportedAppUsage
@@ -130,7 +157,7 @@
@UnsupportedAppUsage
public String iconPath;
@UnsupportedAppUsage
- public int flags;
+ public @UserInfoFlag int flags;
@UnsupportedAppUsage
public long creationTime;
@UnsupportedAppUsage
@@ -214,6 +241,10 @@
return (flags & FLAG_DEMO) == FLAG_DEMO;
}
+ public boolean isFull() {
+ return (flags & FLAG_FULL) == FLAG_FULL;
+ }
+
/**
* Returns true if the user is a split system user.
* <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
@@ -290,13 +321,23 @@
@Override
public String toString() {
+ // NOTE: do not change this string, it's used by 'pm list users', which in turn is
+ // used and parsed by TestDevice. In other words, if you change it, you'd have to change
+ // TestDevice, TestDeviceTest, and possibly others....
return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
}
+ /** @hide */
+ public static String flagsToString(int flags) {
+ return DebugUtils.flagsToString(UserInfo.class, "FLAG_", flags);
+ }
+
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(id);
dest.writeString(name);
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 2e3b000..f302263 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -16,6 +16,7 @@
package android.os;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
@@ -224,4 +225,10 @@
/** @return a specific user restriction that's in effect currently. */
public abstract boolean hasUserRestriction(String restriction, int userId);
+
+ /**
+ * Gets an {@link UserInfo} for the given {@code userId}, or {@code null} if not
+ * found.
+ */
+ public abstract @Nullable UserInfo getUserInfo(@UserIdInt int userId);
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 8e9e74e..4facf4ea 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -18,12 +18,17 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserManager;
+import com.android.server.pm.UserManagerService;
+
/**
* The base class for services running in the system process. Override and implement
* the lifecycle event callback methods as needed.
@@ -145,11 +150,29 @@
public void onBootPhase(int phase) {}
/**
+ * @deprecated subclasses should extend {@link #onStartUser(int, int)} instead (which by default
+ * calls this method).
+ */
+ @Deprecated
+ public void onStartUser(@UserIdInt int userHandle) {}
+
+ /**
* Called when a new user is starting, for system services to initialize any per-user
* state they maintain for running users.
- * @param userHandle The identifier of the user.
+ *
+ * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
*/
- public void onStartUser(int userHandle) {}
+ public void onStartUser(@NonNull UserInfo userInfo) {
+ onStartUser(userInfo.id);
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onUnlockUser(int, int)} instead (which by
+ * default calls this method).
+ */
+ @Deprecated
+ public void onUnlockUser(@UserIdInt int userHandle) {}
/**
* Called when an existing user is in the process of being unlocked. This
@@ -163,17 +186,38 @@
* {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
* these states.
*
- * @param userHandle The identifier of the user.
+ * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
*/
- public void onUnlockUser(int userHandle) {}
+ public void onUnlockUser(@NonNull UserInfo userInfo) {
+ onUnlockUser(userInfo.id);
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onSwitchUser(int, int)} instead (which by
+ * default calls this method).
+ */
+ @Deprecated
+ public void onSwitchUser(@UserIdInt int userHandle) {}
/**
* Called when switching to a different foreground user, for system services that have
* special behavior for whichever user is currently in the foreground. This is called
* before any application processes are aware of the new user.
- * @param userHandle The identifier of the user.
+ *
+ * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
*/
- public void onSwitchUser(int userHandle) {}
+ public void onSwitchUser(@NonNull UserInfo userInfo) {
+ onSwitchUser(userInfo.id);
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onStopUser(int, int)} instead (which by default
+ * calls this method).
+ */
+ @Deprecated
+ public void onStopUser(@UserIdInt int userHandle) {}
/**
* Called when an existing user is stopping, for system services to finalize any per-user
@@ -183,9 +227,19 @@
*
* <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
*
- * @param userHandle The identifier of the user.
+ * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
*/
- public void onStopUser(int userHandle) {}
+ public void onStopUser(@NonNull UserInfo userInfo) {
+ onStopUser(userInfo.id);
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onCleanupUser(int, int)} instead (which by
+ * default calls this method).
+ */
+ @Deprecated
+ public void onCleanupUser(@UserIdInt int userHandle) {}
/**
* Called when an existing user is stopping, for system services to finalize any per-user
@@ -193,11 +247,14 @@
* teardown of the user is complete.
*
* <p>NOTE: When this callback is called, the CE storage for the target user may not be
- * accessible already. Use {@link #onStopUser} instead if you need to access the CE storage.
+ * accessible already. Use {@link #onCleanupUser} instead if you need to access the CE storage.
*
- * @param userHandle The identifier of the user.
+ * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
*/
- public void onCleanupUser(int userHandle) {}
+ public void onCleanupUser(@NonNull UserInfo userInfo) {
+ onCleanupUser(userInfo.id);
+ }
/**
* Publish the service so it is accessible to other services and apps.
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 9711152..ebe23f6 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -19,9 +19,11 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserManagerInternal;
import android.util.Slog;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -53,6 +55,8 @@
private int mCurrentPhase = -1;
+ private UserManagerInternal mUserManagerInternal;
+
SystemServiceManager(Context context) {
mContext = context;
}
@@ -187,11 +191,30 @@
}
/**
+ * Called at the beginning of {@code ActivityManagerService.systemReady()}.
+ */
+ public void preSystemReady() {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+
+ private @NonNull UserInfo getUserInfo(@UserIdInt int userHandle) {
+ if (mUserManagerInternal == null) {
+ throw new IllegalStateException("mUserManagerInternal not set yet");
+ }
+ final UserInfo userInfo = mUserManagerInternal.getUserInfo(userHandle);
+ if (userInfo == null) {
+ throw new IllegalStateException("No UserInfo for " + userHandle);
+ }
+ return userInfo;
+ }
+
+ /**
* Starts the given user.
*/
- public void startUser(@NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
+ public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
t.traceBegin("ssm.startUser-" + userHandle);
Slog.i(TAG, "Calling onStartUser u" + userHandle);
+ final UserInfo userInfo = getUserInfo(userHandle);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
@@ -199,7 +222,7 @@
t.traceBegin("onStartUser-" + userHandle + " " + serviceName);
long time = SystemClock.elapsedRealtime();
try {
- service.onStartUser(userHandle);
+ service.onStartUser(userInfo);
} catch (Exception ex) {
Slog.wtf(TAG, "Failure reporting start of user " + userHandle
+ " to service " + service.getClass().getName(), ex);
@@ -217,6 +240,7 @@
final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
t.traceBegin("ssm.unlockUser-" + userHandle);
Slog.i(TAG, "Calling onUnlockUser u" + userHandle);
+ final UserInfo userInfo = getUserInfo(userHandle);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
@@ -224,7 +248,7 @@
t.traceBegin("onUnlockUser-" + userHandle + " " + serviceName);
long time = SystemClock.elapsedRealtime();
try {
- service.onUnlockUser(userHandle);
+ service.onUnlockUser(userInfo);
} catch (Exception ex) {
Slog.wtf(TAG, "Failure reporting unlock of user " + userHandle
+ " to service " + serviceName, ex);
@@ -242,6 +266,7 @@
final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
t.traceBegin("ssm.switchUser-" + userHandle);
Slog.i(TAG, "Calling switchUser u" + userHandle);
+ final UserInfo userInfo = getUserInfo(userHandle);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
@@ -249,7 +274,7 @@
t.traceBegin("onSwitchUser-" + userHandle + " " + serviceName);
long time = SystemClock.elapsedRealtime();
try {
- service.onSwitchUser(userHandle);
+ service.onSwitchUser(userInfo);
} catch (Exception ex) {
Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
+ " to service " + serviceName, ex);
@@ -267,6 +292,7 @@
final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
t.traceBegin("ssm.stopUser-" + userHandle);
Slog.i(TAG, "Calling onStopUser u" + userHandle);
+ final UserInfo userInfo = getUserInfo(userHandle);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
@@ -274,7 +300,7 @@
t.traceBegin("onStopUser-" + userHandle + " " + serviceName);
long time = SystemClock.elapsedRealtime();
try {
- service.onStopUser(userHandle);
+ service.onStopUser(userInfo);
} catch (Exception ex) {
Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
+ " to service " + serviceName, ex);
@@ -292,6 +318,7 @@
final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
t.traceBegin("ssm.cleanupUser-" + userHandle);
Slog.i(TAG, "Calling onCleanupUser u" + userHandle);
+ final UserInfo userInfo = getUserInfo(userHandle);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
@@ -299,7 +326,7 @@
t.traceBegin("onCleanupUser-" + userHandle + " " + serviceName);
long time = SystemClock.elapsedRealtime();
try {
- service.onCleanupUser(userHandle);
+ service.onCleanupUser(userInfo);
} catch (Exception ex) {
Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+ " to service " + serviceName, ex);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ede573a..4f2cddd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8990,6 +8990,7 @@
*/
public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) {
t.traceBegin("PhaseActivityManagerReady");
+ mSystemServiceManager.preSystemReady();
synchronized(this) {
if (mSystemReady) {
// If we're done calling all the receivers, run the next "boot phase" passed in
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2ccb6c1..ead9d7a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3762,6 +3762,8 @@
pw.print(" <partial>");
}
pw.println();
+ pw.print(" Flags: "); pw.print(userInfo.flags); pw.print(" (");
+ pw.print(UserInfo.flagsToString(userInfo.flags)); pw.println(")");
pw.print(" State: ");
final int state;
synchronized (mUserStates) {
@@ -3846,6 +3848,8 @@
pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean(
com.android.internal.R.bool.config_guestUserEphemeral));
pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
+ pw.println(" Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
+ pw.println(" User version: " + mUserVersion);
}
private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
@@ -4172,6 +4176,14 @@
Bundle restrictions = getEffectiveUserRestrictions(userId);
return restrictions != null && restrictions.getBoolean(restrictionKey);
}
+
+ public @Nullable UserInfo getUserInfo(@UserIdInt int userId) {
+ UserData userData;
+ synchronized (mUsersLock) {
+ userData = mUsers.get(userId);
+ }
+ return userData == null ? null : userData.info;
+ }
}
/* Remove all the users except of the system one. */
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index b2fde54..b2ac549 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -20,6 +20,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
@@ -36,6 +37,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.ShortcutServiceInternal;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
@@ -80,6 +82,7 @@
import com.android.server.UiThread;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
@@ -92,7 +95,7 @@
*/
public class VoiceInteractionManagerService extends SystemService {
static final String TAG = "VoiceInteractionManagerService";
- static final boolean DEBUG = false;
+ static final boolean DEBUG = true; // TODO(b/133242016) STOPSHIP: change to false before R ships
final Context mContext;
final ContentResolver mResolver;
@@ -154,19 +157,37 @@
}
@Override
- public void onStartUser(int userHandle) {
- mServiceStub.initForUser(userHandle);
+ public void onStartUser(@NonNull UserInfo userInfo) {
+ if (DEBUG) Slog.d(TAG, "onStartUser(" + userInfo + ")");
+
+ if (!userInfo.isFull()) {
+ if (DEBUG) Slog.d(TAG, "***** skipping on non-full user " + userInfo);
+ return;
+ }
+ mServiceStub.initForUser(userInfo.id);
}
@Override
- public void onUnlockUser(int userHandle) {
- mServiceStub.initForUser(userHandle);
+ public void onUnlockUser(@NonNull UserInfo userInfo) {
+ if (DEBUG) Slog.d(TAG, "onUnlockUser(" + userInfo + ")");
+
+ if (!userInfo.isFull()) {
+ if (DEBUG) Slog.d(TAG, "***** skipping on non-full user " + userInfo);
+ return;
+ }
+ mServiceStub.initForUser(userInfo.id);
mServiceStub.switchImplementationIfNeeded(false);
}
@Override
- public void onSwitchUser(int userHandle) {
- mServiceStub.switchUser(userHandle);
+ public void onSwitchUser(@NonNull UserInfo userInfo) {
+ if (DEBUG) Slog.d(TAG, "onSwitchUser(" + userInfo + ")");
+
+ if (!userInfo.isFull()) {
+ if (DEBUG) Slog.d(TAG, "***** skipping on non-full user " + userInfo);
+ return;
+ }
+ mServiceStub.switchUser(userInfo.id);
}
class LocalService extends VoiceInteractionManagerInternal {
@@ -270,6 +291,20 @@
}
public void initForUser(int userHandle) {
+ final TimingsTraceAndSlog t;
+ if (DEBUG) {
+ t = TimingsTraceAndSlog.newAsyncLog();
+ t.traceBegin("VoiceInteractionSvc.initForUser(" + userHandle + ")");
+ } else {
+ t = null;
+ }
+ initForUserNoTracing(userHandle);
+ if (t != null) {
+ t.traceEnd();
+ }
+ }
+
+ private void initForUserNoTracing(@UserIdInt int userHandle) {
if (DEBUG) Slog.d(TAG, "**************** initForUser user=" + userHandle);
String curInteractorStr = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
@@ -426,6 +461,20 @@
}
void switchImplementationIfNeededLocked(boolean force) {
+ final TimingsTraceAndSlog t;
+ if (DEBUG) {
+ t = TimingsTraceAndSlog.newAsyncLog();
+ t.traceBegin("VoiceInteractionSvc.switchImplementation(" + mCurUser + ")");
+ } else {
+ t = null;
+ }
+ switchImplementationIfNeededNoTracingLocked(force);
+ if (t != null) {
+ t.traceEnd();
+ }
+ }
+
+ void switchImplementationIfNeededNoTracingLocked(boolean force) {
if (!mSafeMode) {
String curService = Settings.Secure.getStringForUser(
mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser);