Loading core/java/android/os/Handler.java +4 −0 Original line number Diff line number Diff line Loading @@ -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 <var>object</var> is null, * all messages will be removed. * <p> * 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 */ Loading services/core/java/com/android/server/SystemService.java +97 −0 Original line number Diff line number Diff line Loading @@ -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. * <p> Loading Loading @@ -406,6 +476,33 @@ public abstract class SystemService { public void onUserStopped(@NonNull TargetUser user) { } /** * Called some time <i>after</i> an onUser... event has completed, for the events delineated in * {@link UserCompletedEventType}. May include more than one event. * * <p> * 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. * * <p>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). * * <p>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. * Loading services/core/java/com/android/server/SystemServiceManager.java +91 −13 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -404,6 +406,26 @@ public final class SystemServiceManager implements Dumpable { } } /** * Called some time <i>after</i> 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)); Loading @@ -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(); Loading @@ -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); } Loading @@ -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); } Loading @@ -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?"); } Loading @@ -498,10 +527,12 @@ 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); 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."); } Loading @@ -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); Loading @@ -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; Loading services/core/java/com/android/server/am/EventLogTags.logtags +1 −0 Original line number Diff line number Diff line Loading @@ -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) services/core/java/com/android/server/am/UserController.java +104 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; Loading Loading @@ -364,6 +374,13 @@ class UserController implements Handler.Callback { @GuardedBy("mUserIdToUserJourneyMap") private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>(); /** * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet- * unreported user-starting events have transpired for the given user. */ @GuardedBy("mCompletedEventTypes") private final SparseIntArray mCompletedEventTypes = new SparseIntArray(); /** * Sets on {@link #setInitialConfig(boolean, int, boolean)}, which is called by * {@code ActivityManager} when the system is started. Loading Loading @@ -2778,6 +2795,9 @@ class UserController implements Handler.Callback { mInjector.getSystemServiceManager().onUserStarting( TimingsTraceAndSlog.newAsyncLog(), msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_STARTING, USER_COMPLETED_EVENT_DELAY_MS); logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER, USER_LIFECYCLE_EVENT_STATE_FINISH); Loading @@ -2802,6 +2822,13 @@ class UserController implements Handler.Callback { break; case USER_UNLOCKED_MSG: mInjector.getSystemServiceManager().onUserUnlocked(msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED, // If it's the foreground user, we wait longer to let it fully load. // Else, there's nothing specific to wait for, so we basically just proceed. // (No need to acquire lock to read mCurrentUserId since it is volatile.) // TODO: Find something to wait for in the case of a profile. mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000); logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER, USER_LIFECYCLE_EVENT_STATE_FINISH); clearSessionId(msg.arg1); Loading @@ -2815,6 +2842,12 @@ class UserController implements Handler.Callback { Integer.toString(msg.arg1), msg.arg1); mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_SWITCHING, USER_COMPLETED_EVENT_DELAY_MS); break; case USER_COMPLETED_EVENT_MSG: reportOnUserCompletedEvent((Integer) msg.obj); break; case FOREGROUND_PROFILE_CHANGED_MSG: dispatchForegroundProfileChanged(msg.arg1); Loading Loading @@ -2846,6 +2879,71 @@ class UserController implements Handler.Callback { return false; } /** * Schedules {@link SystemServiceManager#onUserCompletedEvent()} with the given * {@link UserCompletedEventType} event, which will be combined with any other events for that * user already scheduled. * If it isn't rescheduled first, it will fire after a delayMs delay. * * @param eventType event type flags from {@link UserCompletedEventType} to append to the * schedule. Use 0 to schedule the ssm call without modifying the event types. */ // TODO(b/197344658): Also call scheduleOnUserCompletedEvent(userId, 0, 0) after switch UX done. @VisibleForTesting void scheduleOnUserCompletedEvent( int userId, @UserCompletedEventType.EventTypesFlag int eventType, int delayMs) { if (eventType != 0) { synchronized (mCompletedEventTypes) { mCompletedEventTypes.put(userId, mCompletedEventTypes.get(userId, 0) | eventType); } } final Object msgObj = userId; mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, msgObj); mHandler.sendMessageDelayed( mHandler.obtainMessage(USER_COMPLETED_EVENT_MSG, msgObj), delayMs); } /** * Calls {@link SystemServiceManager#onUserCompletedEvent()} for the given user, sending all the * {@link UserCompletedEventType} events that have been scheduled for it if they are still * applicable. * * Called on the mHandler thread. */ @VisibleForTesting void reportOnUserCompletedEvent(Integer userId) { mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, userId); int eventTypes; synchronized (mCompletedEventTypes) { eventTypes = mCompletedEventTypes.get(userId, 0); mCompletedEventTypes.delete(userId); } // Now, remove any eventTypes that are no longer true. int eligibleEventTypes = 0; synchronized (mLock) { final UserState uss = mStartedUsers.get(userId); if (uss != null && uss.state != UserState.STATE_SHUTDOWN) { eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_STARTING; } if (uss != null && uss.state == STATE_RUNNING_UNLOCKED) { eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED; } if (userId == mCurrentUserId) { eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_SWITCHING; } } Slogf.i(TAG, "reportOnUserCompletedEvent(%d): stored=%s, eligible=%s", userId, Integer.toBinaryString(eventTypes), Integer.toBinaryString(eligibleEventTypes)); eventTypes &= eligibleEventTypes; mInjector.systemServiceManagerOnUserCompletedEvent(userId, eventTypes); } /** * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred * atom given the originating and targeting users for the journey. Loading Loading @@ -3086,7 +3184,11 @@ class UserController implements Handler.Callback { } void systemServiceManagerOnUserStopped(@UserIdInt int userId) { mService.mSystemServiceManager.onUserStopped(userId); getSystemServiceManager().onUserStopped(userId); } void systemServiceManagerOnUserCompletedEvent(@UserIdInt int userId, int eventTypes) { getSystemServiceManager().onUserCompletedEvent(userId, eventTypes); } protected UserManagerService getUserManager() { Loading @@ -3113,7 +3215,7 @@ class UserController implements Handler.Callback { } boolean isRuntimeRestarted() { return mService.mSystemServiceManager.isRuntimeRestarted(); return getSystemServiceManager().isRuntimeRestarted(); } SystemServiceManager getSystemServiceManager() { Loading Loading
core/java/android/os/Handler.java +4 −0 Original line number Diff line number Diff line Loading @@ -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 <var>object</var> is null, * all messages will be removed. * <p> * 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 */ Loading
services/core/java/com/android/server/SystemService.java +97 −0 Original line number Diff line number Diff line Loading @@ -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. * <p> Loading Loading @@ -406,6 +476,33 @@ public abstract class SystemService { public void onUserStopped(@NonNull TargetUser user) { } /** * Called some time <i>after</i> an onUser... event has completed, for the events delineated in * {@link UserCompletedEventType}. May include more than one event. * * <p> * 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. * * <p>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). * * <p>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. * Loading
services/core/java/com/android/server/SystemServiceManager.java +91 −13 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -404,6 +406,26 @@ public final class SystemServiceManager implements Dumpable { } } /** * Called some time <i>after</i> 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)); Loading @@ -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(); Loading @@ -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); } Loading @@ -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); } Loading @@ -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?"); } Loading @@ -498,10 +527,12 @@ 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); 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."); } Loading @@ -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); Loading @@ -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; Loading
services/core/java/com/android/server/am/EventLogTags.logtags +1 −0 Original line number Diff line number Diff line Loading @@ -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)
services/core/java/com/android/server/am/UserController.java +104 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; Loading Loading @@ -364,6 +374,13 @@ class UserController implements Handler.Callback { @GuardedBy("mUserIdToUserJourneyMap") private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>(); /** * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet- * unreported user-starting events have transpired for the given user. */ @GuardedBy("mCompletedEventTypes") private final SparseIntArray mCompletedEventTypes = new SparseIntArray(); /** * Sets on {@link #setInitialConfig(boolean, int, boolean)}, which is called by * {@code ActivityManager} when the system is started. Loading Loading @@ -2778,6 +2795,9 @@ class UserController implements Handler.Callback { mInjector.getSystemServiceManager().onUserStarting( TimingsTraceAndSlog.newAsyncLog(), msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_STARTING, USER_COMPLETED_EVENT_DELAY_MS); logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER, USER_LIFECYCLE_EVENT_STATE_FINISH); Loading @@ -2802,6 +2822,13 @@ class UserController implements Handler.Callback { break; case USER_UNLOCKED_MSG: mInjector.getSystemServiceManager().onUserUnlocked(msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED, // If it's the foreground user, we wait longer to let it fully load. // Else, there's nothing specific to wait for, so we basically just proceed. // (No need to acquire lock to read mCurrentUserId since it is volatile.) // TODO: Find something to wait for in the case of a profile. mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000); logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER, USER_LIFECYCLE_EVENT_STATE_FINISH); clearSessionId(msg.arg1); Loading @@ -2815,6 +2842,12 @@ class UserController implements Handler.Callback { Integer.toString(msg.arg1), msg.arg1); mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_SWITCHING, USER_COMPLETED_EVENT_DELAY_MS); break; case USER_COMPLETED_EVENT_MSG: reportOnUserCompletedEvent((Integer) msg.obj); break; case FOREGROUND_PROFILE_CHANGED_MSG: dispatchForegroundProfileChanged(msg.arg1); Loading Loading @@ -2846,6 +2879,71 @@ class UserController implements Handler.Callback { return false; } /** * Schedules {@link SystemServiceManager#onUserCompletedEvent()} with the given * {@link UserCompletedEventType} event, which will be combined with any other events for that * user already scheduled. * If it isn't rescheduled first, it will fire after a delayMs delay. * * @param eventType event type flags from {@link UserCompletedEventType} to append to the * schedule. Use 0 to schedule the ssm call without modifying the event types. */ // TODO(b/197344658): Also call scheduleOnUserCompletedEvent(userId, 0, 0) after switch UX done. @VisibleForTesting void scheduleOnUserCompletedEvent( int userId, @UserCompletedEventType.EventTypesFlag int eventType, int delayMs) { if (eventType != 0) { synchronized (mCompletedEventTypes) { mCompletedEventTypes.put(userId, mCompletedEventTypes.get(userId, 0) | eventType); } } final Object msgObj = userId; mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, msgObj); mHandler.sendMessageDelayed( mHandler.obtainMessage(USER_COMPLETED_EVENT_MSG, msgObj), delayMs); } /** * Calls {@link SystemServiceManager#onUserCompletedEvent()} for the given user, sending all the * {@link UserCompletedEventType} events that have been scheduled for it if they are still * applicable. * * Called on the mHandler thread. */ @VisibleForTesting void reportOnUserCompletedEvent(Integer userId) { mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, userId); int eventTypes; synchronized (mCompletedEventTypes) { eventTypes = mCompletedEventTypes.get(userId, 0); mCompletedEventTypes.delete(userId); } // Now, remove any eventTypes that are no longer true. int eligibleEventTypes = 0; synchronized (mLock) { final UserState uss = mStartedUsers.get(userId); if (uss != null && uss.state != UserState.STATE_SHUTDOWN) { eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_STARTING; } if (uss != null && uss.state == STATE_RUNNING_UNLOCKED) { eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED; } if (userId == mCurrentUserId) { eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_SWITCHING; } } Slogf.i(TAG, "reportOnUserCompletedEvent(%d): stored=%s, eligible=%s", userId, Integer.toBinaryString(eventTypes), Integer.toBinaryString(eligibleEventTypes)); eventTypes &= eligibleEventTypes; mInjector.systemServiceManagerOnUserCompletedEvent(userId, eventTypes); } /** * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred * atom given the originating and targeting users for the journey. Loading Loading @@ -3086,7 +3184,11 @@ class UserController implements Handler.Callback { } void systemServiceManagerOnUserStopped(@UserIdInt int userId) { mService.mSystemServiceManager.onUserStopped(userId); getSystemServiceManager().onUserStopped(userId); } void systemServiceManagerOnUserCompletedEvent(@UserIdInt int userId, int eventTypes) { getSystemServiceManager().onUserCompletedEvent(userId, eventTypes); } protected UserManagerService getUserManager() { Loading @@ -3113,7 +3215,7 @@ class UserController implements Handler.Callback { } boolean isRuntimeRestarted() { return mService.mSystemServiceManager.isRuntimeRestarted(); return getSystemServiceManager().isRuntimeRestarted(); } SystemServiceManager getSystemServiceManager() { Loading