SystemService post-completed user event
Introduces a new SystemService stage that indicates that user events (user-switch, user-starting, user-unlocked) have completed. This can be used by services for non-urgent tasks that must be completed after the event in question, but can be delayed until later. As long as they happen close enough in time, only a single event will be sent (per user) which will contain information about all the onUser-completion events it represents. TODO: Also trigger the call when the user-switch UX has completed (rather than waiting for the hard-coded delay). First we need to find an appropriate signal for "switch UX is complete". Test: atest UserControllerTest Test: manual confirmation using VoiceInteractionManagerService Bug: 197344658 Change-Id: Id8e6ce8e9c46f86d48d0a76085123c0c3dd1f2ea
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -33,6 +33,7 @@ import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
|
||||
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
|
||||
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
|
||||
import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
|
||||
import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
|
||||
import static com.android.server.am.UserController.USER_CURRENT_MSG;
|
||||
import static com.android.server.am.UserController.USER_START_MSG;
|
||||
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
|
||||
@@ -45,6 +46,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Matchers.any;
|
||||
@@ -91,6 +93,7 @@ import android.util.Log;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.server.FgThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.am.UserState.KeyEvictedCallback;
|
||||
import com.android.server.pm.UserManagerInternal;
|
||||
import com.android.server.pm.UserManagerService;
|
||||
@@ -167,6 +170,8 @@ public class UserControllerTest {
|
||||
doReturn(false).when(mInjector).taskSupervisorSwitchUser(anyInt(), any());
|
||||
doNothing().when(mInjector).taskSupervisorResumeFocusedStackTopActivity();
|
||||
doNothing().when(mInjector).systemServiceManagerOnUserStopped(anyInt());
|
||||
doNothing().when(mInjector).systemServiceManagerOnUserCompletedEvent(
|
||||
anyInt(), anyInt());
|
||||
doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString());
|
||||
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
|
||||
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
|
||||
@@ -719,6 +724,54 @@ public class UserControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScheduleOnUserCompletedEvent() throws Exception {
|
||||
// user1 is starting, switching, and unlocked, but not scheduled unlocked yet
|
||||
// user2 is starting and had unlocked but isn't unlocked anymore for whatever reason
|
||||
|
||||
final int user1 = 101;
|
||||
final int user2 = 102;
|
||||
setUpUser(user1, 0);
|
||||
setUpUser(user2, 0);
|
||||
|
||||
mUserController.startUser(user1, /* foreground= */ true);
|
||||
mUserController.getStartedUserState(user1).setState(UserState.STATE_RUNNING_UNLOCKED);
|
||||
|
||||
mUserController.startUser(user2, /* foreground= */ false);
|
||||
mUserController.getStartedUserState(user2).setState(UserState.STATE_RUNNING_LOCKED);
|
||||
|
||||
final int event1a = SystemService.UserCompletedEventType.EVENT_TYPE_USER_STARTING;
|
||||
final int event1b = SystemService.UserCompletedEventType.EVENT_TYPE_USER_SWITCHING;
|
||||
|
||||
final int event2a = SystemService.UserCompletedEventType.EVENT_TYPE_USER_STARTING;
|
||||
final int event2b = SystemService.UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED;
|
||||
|
||||
|
||||
mUserController.scheduleOnUserCompletedEvent(user1, event1a, 2000);
|
||||
assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user1));
|
||||
assertNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user2));
|
||||
|
||||
mUserController.scheduleOnUserCompletedEvent(user2, event2a, 2000);
|
||||
assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user1));
|
||||
assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user2));
|
||||
|
||||
mUserController.scheduleOnUserCompletedEvent(user2, event2b, 2000);
|
||||
mUserController.scheduleOnUserCompletedEvent(user1, event1b, 2000);
|
||||
mUserController.scheduleOnUserCompletedEvent(user1, 0, 2000);
|
||||
|
||||
assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user1));
|
||||
assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user2));
|
||||
|
||||
mUserController.reportOnUserCompletedEvent(user1);
|
||||
verify(mInjector, times(1))
|
||||
.systemServiceManagerOnUserCompletedEvent(eq(user1), eq(event1a | event1b));
|
||||
verify(mInjector, never()).systemServiceManagerOnUserCompletedEvent(eq(user2), anyInt());
|
||||
|
||||
mUserController.reportOnUserCompletedEvent(user2);
|
||||
verify(mInjector, times(1))
|
||||
.systemServiceManagerOnUserCompletedEvent(eq(user2), eq(event2a));
|
||||
}
|
||||
|
||||
private void setUpAndStartUserInBackground(int userId) throws Exception {
|
||||
setUpUser(userId, 0);
|
||||
mUserController.startUser(userId, /* foreground= */ false);
|
||||
@@ -969,8 +1022,12 @@ public class UserControllerTest {
|
||||
}
|
||||
|
||||
Message getMessageForCode(int what) {
|
||||
return getMessageForCode(what, null);
|
||||
}
|
||||
|
||||
Message getMessageForCode(int what, Object obj) {
|
||||
for (Message msg : mMessages) {
|
||||
if (msg.what == what) {
|
||||
if (msg.what == what && (obj == null || obj.equals(msg.obj))) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user