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;