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/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. */