diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index d310d6e7530d7..834867cab3a85 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -799,6 +799,10 @@ public class Handler { * Remove any pending posts of messages with code 'what' and whose obj is * 'object' that are in the message queue. If object is null, * all messages will be removed. + *
+ * Similar to {@link #removeMessages(int, Object)} but uses object equality + * ({@link Object#equals(Object)}) instead of reference equality (==) in + * determining whether object is the message's obj'. * *@hide */ diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index 99232746cea16..ed545a6f720c6 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -226,6 +226,76 @@ public abstract class SystemService { } } + /** + * Class representing the types of "onUser" events that we are being informed about as having + * finished. + * + * @hide + */ + public static final class UserCompletedEventType { + /** + * Flag representing the {@link #onUserStarting} event. + * @hide + */ + public static final int EVENT_TYPE_USER_STARTING = 1 << 0; + /** + * Flag representing the {@link #onUserUnlocked} event. + * @hide + */ + public static final int EVENT_TYPE_USER_UNLOCKED = 1 << 1; + /** + * Flag representing the {@link #onUserSwitching} event. + * @hide + */ + public static final int EVENT_TYPE_USER_SWITCHING = 1 << 2; + + /** + * @hide + */ + @IntDef(flag = true, prefix = "EVENT_TYPE_USER_", value = { + EVENT_TYPE_USER_STARTING, + EVENT_TYPE_USER_UNLOCKED, + EVENT_TYPE_USER_SWITCHING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventTypesFlag { + } + + private @EventTypesFlag int mEventType; + + /** @hide */ + UserCompletedEventType(@EventTypesFlag int eventType) { + mEventType = eventType; + } + + /** Returns whether one of the events is {@link #onUserStarting}. */ + public boolean includesOnUserStarting() { + return (mEventType & EVENT_TYPE_USER_STARTING) != 0; + } + + /** Returns whether one of the events is {@link #onUserUnlocked}. */ + public boolean includesOnUserUnlocked() { + return (mEventType & EVENT_TYPE_USER_UNLOCKED) != 0; + } + + /** Returns whether one of the events is {@link #onUserSwitching}. */ + public boolean includesOnUserSwitching() { + return (mEventType & EVENT_TYPE_USER_SWITCHING) != 0; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + // List each in reverse order (to line up with binary better). + if (includesOnUserSwitching()) sb.append("|Switching"); + if (includesOnUserUnlocked()) sb.append("|Unlocked"); + if (includesOnUserStarting()) sb.append("|Starting"); + if (sb.length() > 1) sb.append("|"); + sb.append("}"); + return sb.toString(); + } + } + /** * Initializes the system service. *
@@ -406,6 +476,33 @@ public abstract class SystemService { public void onUserStopped(@NonNull TargetUser user) { } + /** + * Called some time after an onUser... event has completed, for the events delineated in + * {@link UserCompletedEventType}. May include more than one event. + * + *
+ * This can be useful for processing tasks that must run after such an event but are non-urgent. + * + * There are no strict guarantees about how long after the event this will be called, only that + * it will be called if applicable. There is no guarantee about the order in which each service + * is informed, and these calls may be made in parallel using a thread pool. + * + *
Note that if the event is no longer applicable (for example, we switched to user 10, but + * before this method was called, we switched to user 11), the event will not be included in the + * {@code eventType} (i.e. user 10 won't mention the switch - even though it happened, it is no + * longer applicable). + * + *
This method is only called when the service {@link #isUserSupported(TargetUser) supports}
+ * this user.
+ *
+ * @param user target user completing the event (e.g. user being switched to)
+ * @param eventType the types of onUser event applicable (e.g. user starting and being unlocked)
+ *
+ * @hide
+ */
+ public void onUserCompletedEvent(@NonNull TargetUser user, UserCompletedEventType eventType) {
+ }
+
/**
* 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 e7f4de2a05883..0bc3fcc83d47c 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -36,6 +36,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SystemServerClassLoaderFactory;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService.TargetUser;
+import com.android.server.SystemService.UserCompletedEventType;
import com.android.server.am.EventLogTags;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -73,6 +74,7 @@ public final class SystemServiceManager implements Dumpable {
private static final String USER_SWITCHING = "Switch"; // Logged as onSwitchUser
private static final String USER_STOPPING = "Stop"; // Logged as onStopUser
private static final String USER_STOPPED = "Cleanup"; // Logged as onCleanupUser
+ private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onCompletedEventUser
// Whether to use multiple threads to run user lifecycle phases in parallel.
private static boolean sUseLifecycleThreadPool = true;
@@ -404,6 +406,26 @@ public final class SystemServiceManager implements Dumpable {
}
}
+ /**
+ * Called some time after an onUser... event has completed, for the events delineated in
+ * {@link UserCompletedEventType}.
+ *
+ * @param eventFlags the events that completed, per {@link UserCompletedEventType}, or 0.
+ * @see SystemService#onUserCompletedEvent
+ */
+ public void onUserCompletedEvent(@UserIdInt int userId,
+ @UserCompletedEventType.EventTypesFlag int eventFlags) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_COMPLETED_EVENT, userId, eventFlags);
+ if (eventFlags == 0) {
+ return;
+ }
+ onUser(TimingsTraceAndSlog.newAsyncLog(),
+ USER_COMPLETED_EVENT,
+ /* prevUser= */ null,
+ getTargetUser(userId),
+ new UserCompletedEventType(eventFlags));
+ }
+
private void onUser(@NonNull String onWhat, @UserIdInt int userId) {
onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null,
getTargetUser(userId));
@@ -411,19 +433,23 @@ public final class SystemServiceManager implements Dumpable {
private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
@Nullable TargetUser prevUser, @NonNull TargetUser curUser) {
+ onUser(t, onWhat, prevUser, curUser, /* completedEventType=*/ null);
+ }
+
+ private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
+ @Nullable TargetUser prevUser, @NonNull TargetUser curUser,
+ @Nullable UserCompletedEventType completedEventType) {
final int curUserId = curUser.getUserIdentifier();
// NOTE: do not change label below, or it might break performance tests that rely on it.
t.traceBegin("ssm." + onWhat + "User-" + curUserId);
Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId
+ (prevUser != null ? " (from " + prevUser + ")" : ""));
- final int serviceLen = mServices.size();
- // Limit the lifecycle parallelization to all users other than the system user
- // and only for the user start lifecycle phase for now.
- final boolean useThreadPool = sUseLifecycleThreadPool
- && curUserId != UserHandle.USER_SYSTEM
- && onWhat.equals(USER_STARTING);
+
+ final boolean useThreadPool = useThreadPool(curUserId, onWhat);
final ExecutorService threadPool =
useThreadPool ? Executors.newFixedThreadPool(mNumUserPoolThreads) : null;
+
+ final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
final String serviceName = service.getClass().getName();
@@ -446,8 +472,7 @@ public final class SystemServiceManager implements Dumpable {
}
continue;
}
- // Only submit this service to the thread pool if it's in the "other" category.
- final boolean submitToThreadPool = useThreadPool && i >= sOtherServicesStartIndex;
+ final boolean submitToThreadPool = useThreadPool && useThreadPoolForService(onWhat, i);
if (!submitToThreadPool) {
t.traceBegin("ssm.on" + onWhat + "User-" + curUserId + "_" + serviceName);
}
@@ -459,7 +484,7 @@ public final class SystemServiceManager implements Dumpable {
break;
case USER_STARTING:
if (submitToThreadPool) {
- threadPool.submit(getOnStartUserRunnable(t, service, curUser));
+ threadPool.submit(getOnUserStartingRunnable(t, service, curUser));
} else {
service.onUserStarting(curUser);
}
@@ -476,6 +501,10 @@ public final class SystemServiceManager implements Dumpable {
case USER_STOPPED:
service.onUserStopped(curUser);
break;
+ case USER_COMPLETED_EVENT:
+ threadPool.submit(getOnUserCompletedEventRunnable(
+ t, service, serviceName, curUser, completedEventType));
+ break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
@@ -498,9 +527,11 @@ public final class SystemServiceManager implements Dumpable {
} catch (InterruptedException e) {
Slog.wtf(TAG, "User lifecycle thread pool was interrupted while awaiting completion"
+ " of " + onWhat + " of user " + curUser, e);
- Slog.e(TAG, "Couldn't terminate, disabling thread pool. "
- + "Please capture a bug report.");
- sUseLifecycleThreadPool = false;
+ if (!onWhat.equals(USER_COMPLETED_EVENT)) {
+ Slog.e(TAG, "Couldn't terminate, disabling thread pool. "
+ + "Please capture a bug report.");
+ sUseLifecycleThreadPool = false;
+ }
}
if (!terminated) {
Slog.wtf(TAG, "User lifecycle thread pool was not terminated.");
@@ -509,7 +540,38 @@ public final class SystemServiceManager implements Dumpable {
t.traceEnd(); // main entry
}
- private Runnable getOnStartUserRunnable(TimingsTraceAndSlog oldTrace, SystemService service,
+ /**
+ * Whether the given onWhat should use a thread pool.
+ * IMPORTANT: changing the logic to return true won't necessarily make it multi-threaded.
+ * There needs to be a corresponding logic change in onUser() to actually submit
+ * to a threadPool for the given onWhat.
+ */
+ private boolean useThreadPool(int userId, @NonNull String onWhat) {
+ switch (onWhat) {
+ case USER_STARTING:
+ // Limit the lifecycle parallelization to all users other than the system user
+ // and only for the user start lifecycle phase for now.
+ return sUseLifecycleThreadPool && userId != UserHandle.USER_SYSTEM;
+ case USER_COMPLETED_EVENT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean useThreadPoolForService(@NonNull String onWhat, int serviceIndex) {
+ switch (onWhat) {
+ case USER_STARTING:
+ // Only submit this service to the thread pool if it's in the "other" category.
+ return serviceIndex >= sOtherServicesStartIndex;
+ case USER_COMPLETED_EVENT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private Runnable getOnUserStartingRunnable(TimingsTraceAndSlog oldTrace, SystemService service,
TargetUser curUser) {
return () -> {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
@@ -531,6 +593,22 @@ public final class SystemServiceManager implements Dumpable {
};
}
+ private Runnable getOnUserCompletedEventRunnable(TimingsTraceAndSlog oldTrace,
+ SystemService service, String serviceName, TargetUser curUser,
+ UserCompletedEventType eventType) {
+ return () -> {
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
+ final int curUserId = curUser.getUserIdentifier();
+ t.traceBegin("ssm.on" + USER_COMPLETED_EVENT + "User-" + curUserId
+ + "_" + eventType + "_" + serviceName);
+ long time = SystemClock.elapsedRealtime();
+ service.onUserCompletedEvent(curUser, eventType);
+ warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
+ "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
+ t.traceEnd();
+ };
+ }
+
/** Sets the safe mode flag for services to query. */
void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 68f2e35094fc8..b250a0cbae9eb 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -115,3 +115,4 @@ option java_package com.android.server.am
30085 ssm_user_unlocked (userId|1|5)
30086 ssm_user_stopping (userId|1|5)
30087 ssm_user_stopped (userId|1|5)
+30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 028a0ec8648a5..86e5521802f5a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -109,6 +109,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.SystemService.UserCompletedEventType;
import com.android.server.SystemServiceManager;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
@@ -166,6 +167,7 @@ class UserController implements Handler.Callback {
static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
+ static final int USER_COMPLETED_EVENT_MSG = 140;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -184,6 +186,14 @@ class UserController implements Handler.Callback {
// when it never calls back.
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
+ /**
+ * Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
+ * scheduled (although it may fire sooner instead).
+ * When it fires, {@link #reportOnUserCompletedEvent} will be processed.
+ */
+ // TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
+ private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;
+
// Used for statsd logging with UserLifecycleJourneyReported + UserLifecycleEventOccurred atoms
private static final long INVALID_SESSION_ID = 0;
@@ -364,6 +374,13 @@ class UserController implements Handler.Callback {
@GuardedBy("mUserIdToUserJourneyMap")
private final SparseArray