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);