Defined a more generic way to let services "opt-out" from certain user types.

SystemService now defines a isSupported(UserInfo), which is used by SystemServiceManager to
skip the service for the unsupported types.

Also added a new argument to SystemService.onSwitch() to indicate which user is
being switched from.

Finally, also fixed VoiceInteractionManagerService so it doesn't start for headless user 0.

Test: manual verification
Test: atest CtsVoiceInteractionTestCases CtsAssistTestCases # on automotive and walleye

Bug: 133242016
Bug: 137878080

Change-Id: Ife4bd875f412f85cccf9c9fd03115abd238d7eab
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index fcdc81c..d03bea2 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -328,6 +328,14 @@
     }
 
     /** @hide */
+    public String toFullString() {
+        return "UserInfo[id=" + id
+                + ", name=" + name
+                + ", flags=" + flagsToString(flags)
+                + "]";
+    }
+
+    /** @hide */
     public static String flagsToString(int flags) {
         return DebugUtils.flagsToString(UserInfo.class, "FLAG_", flags);
     }
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 4facf4ea..4151c72 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -54,6 +54,10 @@
  * {@hide}
  */
 public abstract class SystemService {
+
+    // TODO(b/133242016) STOPSHIP: change to false before R ships
+    protected static final boolean DEBUG_USER = true;
+
     /*
      * Boot Phases
      */
@@ -150,7 +154,17 @@
     public void onBootPhase(int phase) {}
 
     /**
-     * @deprecated subclasses should extend {@link #onStartUser(int, int)} instead (which by default
+     * Checks if the service should be available for the given user.
+     *
+     * <p>By default returns {@code true}, but subclasses should extend for optimization, if they
+     * don't support some types (like headless system user).
+     */
+    public boolean isSupported(@NonNull UserInfo userInfo) {
+        return true;
+    }
+
+    /**
+     * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
      * calls this method).
      */
     @Deprecated
@@ -160,6 +174,9 @@
      * Called when a new user is starting, for system services to initialize any per-user
      * state they maintain for running users.
      *
+     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
+     * 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.
      */
@@ -168,7 +185,7 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onUnlockUser(int, int)} instead (which by
+     * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by
      * default calls this method).
      */
     @Deprecated
@@ -185,6 +202,9 @@
      * Code written inside system services should use
      * {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
      * these states.
+     * <p>
+     * This method is only called when the service {@link #isSupported(UserInfo) supports} this
+     * 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.
@@ -194,8 +214,8 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onSwitchUser(int, int)} instead (which by
-     * default calls this method).
+     * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead
+     * (which by default calls this method).
      */
     @Deprecated
     public void onSwitchUser(@UserIdInt int userHandle) {}
@@ -205,15 +225,21 @@
      * special behavior for whichever user is currently in the foreground.  This is called
      * before any application processes are aware of the new user.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
+     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either
+     * of the users ({@code from} or {@code to}).
+     *
+     * <b>NOTE: </b> both {@code from} and {@code to} are "live" objects
      * referenced by {@link UserManagerService} and hence should not be modified.
+     *
+     * @param from The information about the user being switched from.
+     * @param to The information about the user being switched from to.
      */
-    public void onSwitchUser(@NonNull UserInfo userInfo) {
-        onSwitchUser(userInfo.id);
+    public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
+        onSwitchUser(to.id);
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onStopUser(int, int)} instead (which by default
+     * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default
      * calls this method).
      */
     @Deprecated
@@ -225,6 +251,9 @@
      * broadcast to the user; it is a good place to stop making use of any resources of that
      * user (such as binding to a service running in the user).
      *
+     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
+     * user.
+     *
      * <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
      *
      * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
@@ -235,7 +264,7 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onCleanupUser(int, int)} instead (which by
+     * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by
      * default calls this method).
      */
     @Deprecated
@@ -246,8 +275,12 @@
      * state they maintain for running users.  This is called after all application process
      * teardown of the user is complete.
      *
+     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
+     * user.
+     *
      * <p>NOTE: When this callback is called, the CE storage for the target user may not be
-     * accessible already.  Use {@link #onCleanupUser} instead if you need to access the CE storage.
+     * accessible already.  Use {@link #onStopUser(UserInfo)} instead if you need to access the CE
+     * storage.
      *
      * @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.
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index b085946..c715798 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -23,6 +23,7 @@
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.util.Slog;
 
@@ -41,8 +42,16 @@
  */
 public class SystemServiceManager {
     private static final String TAG = "SystemServiceManager";
+    private static final boolean DEBUG = false;
     private static final int SERVICE_CALL_WARN_TIME_MS = 50;
 
+    // Constants used on onUser(...)
+    private static final String START = "Start";
+    private static final String UNLOCK = "Unlock";
+    private static final String SWITCH = "Switch";
+    private static final String STOP = "Stop";
+    private static final String CLEANUP = "Cleanup";
+
     private static File sSystemDir;
     private final Context mContext;
     private boolean mSafeMode;
@@ -90,7 +99,6 @@
      * @return The service instance, never null.
      * @throws RuntimeException if the service fails to start.
      */
-    @SuppressWarnings("unchecked")
     public <T extends SystemService> T startService(Class<T> serviceClass) {
         try {
             final String name = serviceClass.getName();
@@ -212,64 +220,104 @@
      * Starts the given user.
      */
     public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
-        onUser(t, "Start", userHandle, (s, u) -> s.onStartUser(u));
+        onUser(t, START, userHandle);
     }
 
     /**
      * Unlocks the given user.
      */
     public void unlockUser(final @UserIdInt int userHandle) {
-        onUser("Unlock", userHandle, (s, u) -> s.onUnlockUser(u));
+        onUser(UNLOCK, userHandle);
     }
 
     /**
      * Switches to the given user.
      */
-    public void switchUser(final @UserIdInt int userHandle) {
-        onUser("Switch", userHandle, (s, u) -> s.onSwitchUser(u));
+    public void switchUser(final @UserIdInt int from, final @UserIdInt int to) {
+        onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from);
     }
 
     /**
      * Stops the given user.
      */
     public void stopUser(final @UserIdInt int userHandle) {
-        onUser("Stop", userHandle, (s, u) -> s.onStopUser(u));
+        onUser(STOP, userHandle);
     }
 
     /**
      * Cleans up the given user.
      */
     public void cleanupUser(final @UserIdInt int userHandle) {
-        onUser("Cleanup", userHandle, (s, u) -> s.onCleanupUser(u));
+        onUser(CLEANUP, userHandle);
     }
 
-    private interface ServiceVisitor {
-        void visit(@NonNull SystemService service, @NonNull UserInfo userInfo);
-    }
-
-    private void onUser(@NonNull String onWhat, @UserIdInt int userHandle,
-            @NonNull ServiceVisitor visitor) {
-        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle, visitor);
+    private void onUser(@NonNull String onWhat, @UserIdInt int userHandle) {
+        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle);
     }
 
     private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
-            @UserIdInt int userHandle, @NonNull ServiceVisitor visitor) {
-        t.traceBegin("ssm." + onWhat + "User-" + userHandle);
-        Slog.i(TAG, "Calling on" + onWhat + "User u" + userHandle);
-        final UserInfo userInfo = getUserInfo(userHandle);
+            @UserIdInt int userHandle) {
+        onUser(t, onWhat, userHandle, UserHandle.USER_NULL);
+    }
+
+    private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
+            @UserIdInt int curUserId, @UserIdInt int prevUserId) {
+        t.traceBegin("ssm." + onWhat + "User-" + curUserId);
+        Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
+        final UserInfo curUserInfo = getUserInfo(curUserId);
+        final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null
+                : getUserInfo(prevUserId);
         final int serviceLen = mServices.size();
         for (int i = 0; i < serviceLen; i++) {
             final SystemService service = mServices.get(i);
             final String serviceName = service.getClass().getName();
-            t.traceBegin("ssm.on" + onWhat + "User-" + userHandle + " " + serviceName);
+            boolean supported = service.isSupported(curUserInfo);
+
+            // Must check if either curUser or prevUser is supported (for example, if switching from
+            // unsupported to supported, we still need to notify the services)
+            if (!supported && prevUserInfo != null) {
+                supported = service.isSupported(prevUserInfo);
+            }
+
+            if (!supported) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service "
+                            + serviceName + " because it's not supported (curUser: "
+                            + curUserInfo + ", prevUser:" + prevUserInfo + ")");
+                } else {
+                    Slog.i(TAG,  "Skipping " + onWhat + "User-" + curUserId + " on "
+                            + serviceName);
+                }
+                continue;
+            }
+            t.traceBegin("ssm.on" + onWhat + "User-" + curUserId + " " + serviceName);
             long time = SystemClock.elapsedRealtime();
             try {
-                visitor.visit(service, userInfo);
+                switch (onWhat) {
+                    case SWITCH:
+                        service.onSwitchUser(prevUserInfo, curUserInfo);
+                        break;
+                    case START:
+                        service.onStartUser(curUserInfo);
+                        break;
+                    case UNLOCK:
+                        service.onUnlockUser(curUserInfo);
+                        break;
+                    case STOP:
+                        service.onStopUser(curUserInfo);
+                        break;
+                    case CLEANUP:
+                        service.onCleanupUser(curUserInfo);
+                        break;
+                    default:
+                        throw new IllegalArgumentException(onWhat + " what?");
+                }
             } catch (Exception ex) {
-                Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + userHandle
+                Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo
                         + " to service " + serviceName, ex);
             }
-            warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "on" + onWhat + "User ");
+            warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
+                    "on" + onWhat + "User-" + curUserId);
             t.traceEnd(); // what on service
         }
         t.traceEnd(); // main entry
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d4ceb5a..b2c40ef 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2180,7 +2180,7 @@
                         BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
                         Integer.toString(msg.arg1), msg.arg1);
 
-                mInjector.getSystemServiceManager().switchUser(msg.arg1);
+                mInjector.getSystemServiceManager().switchUser(msg.arg2, msg.arg1);
                 break;
             case FOREGROUND_PROFILE_CHANGED_MSG:
                 dispatchForegroundProfileChanged(msg.arg1);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index b2ac549..4c3449d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -52,8 +52,9 @@
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -95,14 +96,14 @@
  */
 public class VoiceInteractionManagerService extends SystemService {
     static final String TAG = "VoiceInteractionManagerService";
-    static final boolean DEBUG = true; // TODO(b/133242016) STOPSHIP: change to false before R ships
+    static final boolean DEBUG = false;
 
     final Context mContext;
     final ContentResolver mResolver;
     final DatabaseHelper mDbHelper;
     final ActivityManagerInternal mAmInternal;
     final ActivityTaskManagerInternal mAtmInternal;
-    final UserManager mUserManager;
+    final UserManagerInternal mUserManagerInternal;
     final ArraySet<Integer> mLoadedKeyphraseIds = new ArraySet<>();
     ShortcutServiceInternal mShortcutServiceInternal;
     SoundTriggerInternal mSoundTriggerInternal;
@@ -120,8 +121,8 @@
                 LocalServices.getService(ActivityManagerInternal.class));
         mAtmInternal = Preconditions.checkNotNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
-        mUserManager = Preconditions.checkNotNull(
-                context.getSystemService(UserManager.class));
+        mUserManagerInternal = Preconditions.checkNotNull(
+                LocalServices.getService(UserManagerInternal.class));
 
         PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
                 PermissionManagerServiceInternal.class);
@@ -157,37 +158,30 @@
     }
 
     @Override
-    public void onStartUser(@NonNull UserInfo userInfo) {
-        if (DEBUG) Slog.d(TAG, "onStartUser(" + userInfo + ")");
+    public boolean isSupported(UserInfo userInfo) {
+        return userInfo.isFull();
+    }
 
-        if (!userInfo.isFull()) {
-            if (DEBUG) Slog.d(TAG,  "***** skipping on non-full user " + userInfo);
-            return;
-        }
+    @Override
+    public void onStartUser(@NonNull UserInfo userInfo) {
+        if (DEBUG_USER) Slog.d(TAG, "onStartUser(" + userInfo + ")");
+
         mServiceStub.initForUser(userInfo.id);
     }
 
     @Override
     public void onUnlockUser(@NonNull UserInfo userInfo) {
-        if (DEBUG) Slog.d(TAG, "onUnlockUser(" + userInfo + ")");
+        if (DEBUG_USER) 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(@NonNull UserInfo userInfo) {
-        if (DEBUG) Slog.d(TAG, "onSwitchUser(" + userInfo + ")");
+    public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
+        if (DEBUG_USER) Slog.d(TAG, "onSwitchUser(" + from + " > " + to + ")");
 
-        if (!userInfo.isFull()) {
-            if (DEBUG) Slog.d(TAG,  "***** skipping on non-full user " + userInfo);
-            return;
-        }
-        mServiceStub.switchUser(userInfo.id);
+        mServiceStub.switchUser(to.id);
     }
 
     class LocalService extends VoiceInteractionManagerInternal {
@@ -225,6 +219,7 @@
         private boolean mSafeMode;
         private int mCurUser;
         private boolean mCurUserUnlocked;
+        private boolean mCurUserSupported;
         private final boolean mEnableService;
 
         VoiceInteractionManagerServiceStub() {
@@ -292,9 +287,9 @@
 
         public void initForUser(int userHandle) {
             final TimingsTraceAndSlog t;
-            if (DEBUG) {
-                t = TimingsTraceAndSlog.newAsyncLog();
-                t.traceBegin("VoiceInteractionSvc.initForUser(" + userHandle + ")");
+            if (DEBUG_USER) {
+                t = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+                t.traceBegin("initForUser(" + userHandle + ")");
             } else {
                 t = null;
             }
@@ -439,15 +434,21 @@
             new SettingsObserver(UiThread.getHandler());
 
             synchronized (this) {
-                mCurUser = ActivityManager.getCurrentUser();
+                setCurrentUserLocked(ActivityManager.getCurrentUser());
                 switchImplementationIfNeededLocked(false);
             }
         }
 
-        public void switchUser(int userHandle) {
+        private void setCurrentUserLocked(@UserIdInt int userHandle) {
+            mCurUser = userHandle;
+            final UserInfo userInfo = mUserManagerInternal.getUserInfo(mCurUser);
+            mCurUserSupported = isSupported(userInfo);
+        }
+
+        public void switchUser(@UserIdInt int userHandle) {
             FgThread.getHandler().post(() -> {
                 synchronized (this) {
-                    mCurUser = userHandle;
+                    setCurrentUserLocked(userHandle);
                     mCurUserUnlocked = false;
                     switchImplementationIfNeededLocked(false);
                 }
@@ -461,10 +462,22 @@
         }
 
         void switchImplementationIfNeededLocked(boolean force) {
+            if (!mCurUserSupported) {
+                if (DEBUG_USER) {
+                    Slog.d(TAG, "switchImplementationIfNeeded(): skipping on unsuported user "
+                            + mCurUser);
+                }
+                if (mImpl != null) {
+                    mImpl.shutdownLocked();
+                    setImplLocked(null);
+                }
+                return;
+            }
+
             final TimingsTraceAndSlog t;
-            if (DEBUG) {
-                t = TimingsTraceAndSlog.newAsyncLog();
-                t.traceBegin("VoiceInteractionSvc.switchImplementation(" + mCurUser + ")");
+            if (DEBUG_USER) {
+                t = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+                t.traceBegin("switchImplementation(" + mCurUser + ")");
             } else {
                 t = null;
             }
@@ -494,7 +507,7 @@
 
                 final boolean hasComponent = serviceComponent != null && serviceInfo != null;
 
-                if (mUserManager.isUserUnlockingOrUnlocked(mCurUser)) {
+                if (mUserManagerInternal.isUserUnlockingOrUnlocked(mCurUser)) {
                     if (hasComponent) {
                         mShortcutServiceInternal.setShortcutHostPackage(TAG,
                                 serviceComponent.getPackageName(), mCurUser);
@@ -1275,6 +1288,9 @@
             synchronized (this) {
                 pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)");
                 pw.println("  mEnableService: " + mEnableService);
+                pw.println("  mCurUser: " + mCurUser);
+                pw.println("  mCurUserUnlocked: " + mCurUserUnlocked);
+                pw.println("  mCurUserSupported: " + mCurUserSupported);
                 if (mImpl == null) {
                     pw.println("  (No active implementation)");
                     return;