Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 60dcd3a2 authored by Adam Bookatz's avatar Adam Bookatz Committed by Android (Google) Code Review
Browse files

Merge "SystemService post-completed user event"

parents db314870 69444e10
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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
     */
+97 −0
Original line number Diff line number Diff line
@@ -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>
@@ -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.
     *
+91 −13
Original line number Diff line number Diff line
@@ -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 <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));
@@ -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,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.");
            }
@@ -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;
+1 −0
Original line number Diff line number Diff line
@@ -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)
+104 −2
Original line number Diff line number Diff line
@@ -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<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.
@@ -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);
@@ -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);
@@ -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);
@@ -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.
@@ -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() {
@@ -3113,7 +3215,7 @@ class UserController implements Handler.Callback {
        }

        boolean isRuntimeRestarted() {
            return mService.mSystemServiceManager.isRuntimeRestarted();
            return getSystemServiceManager().isRuntimeRestarted();
        }

        SystemServiceManager getSystemServiceManager() {
Loading