Merge "fix: Refactor User Lifecycle Event and Report metrics logging" into udc-dev

This commit is contained in:
Anna Bauza
2023-05-02 17:18:10 +00:00
committed by Android (Google) Code Review
7 changed files with 1289 additions and 438 deletions

View File

@@ -65,7 +65,6 @@ import android.util.Log;
import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -2532,38 +2531,6 @@ public class UserManager {
return USER_TYPE_PROFILE_CLONE.equals(userType); return USER_TYPE_PROFILE_CLONE.equals(userType);
} }
/**
* Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
* user type.
* @hide
*/
public static int getUserTypeForStatsd(@NonNull String userType) {
switch (userType) {
case USER_TYPE_FULL_SYSTEM:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
case USER_TYPE_FULL_SECONDARY:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
case USER_TYPE_FULL_GUEST:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
case USER_TYPE_FULL_DEMO:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
case USER_TYPE_FULL_RESTRICTED:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
case USER_TYPE_PROFILE_MANAGED:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
case USER_TYPE_SYSTEM_HEADLESS:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
case USER_TYPE_PROFILE_CLONE:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
default:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
}
}
/** /**
* @hide * @hide
* @deprecated Use {@link #isRestrictedProfile()} * @deprecated Use {@link #isRestrictedProfile()}

View File

@@ -67,7 +67,6 @@ import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.Trace; import android.os.Trace;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.EventLog; import android.util.EventLog;
import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter;
@@ -78,6 +77,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.os.TimeoutRecord; import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices; import com.android.server.LocalServices;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal;
import dalvik.annotation.optimization.NeverCompile; import dalvik.annotation.optimization.NeverCompile;
@@ -1518,7 +1518,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
final UserInfo userInfo = final UserInfo userInfo =
(umInternal != null) ? umInternal.getUserInfo(r.userId) : null; (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
if (userInfo != null) { if (userInfo != null) {
userType = UserManager.getUserTypeForStatsd(userInfo.userType); userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
} }
Slog.i(TAG_BROADCAST, Slog.i(TAG_BROADCAST,
"BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:" "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"

View File

@@ -46,13 +46,24 @@ import static com.android.server.am.UserState.STATE_BOOTING;
import static com.android.server.am.UserState.STATE_RUNNING_LOCKED; import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED; import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING; import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_SESSION_ID;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKED_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE; import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE; import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND; import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString; import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import static com.android.server.pm.UserManagerInternal.userStartModeToString; import static com.android.server.pm.UserManagerInternal.userStartModeToString;
import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission; import android.annotation.RequiresPermission;
@@ -123,6 +134,8 @@ import com.android.server.LocalServices;
import com.android.server.SystemService.UserCompletedEventType; import com.android.server.SystemService.UserCompletedEventType;
import com.android.server.SystemServiceManager; import com.android.server.SystemServiceManager;
import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserJourneyLogger.UserJourneySession;
import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener; import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserStartMode; import com.android.server.pm.UserManagerInternal.UserStartMode;
@@ -138,7 +151,6 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -221,75 +233,6 @@ class UserController implements Handler.Callback {
// TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too. // 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; 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;
// The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
private static final int USER_JOURNEY_UNKNOWN =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
private static final int USER_JOURNEY_USER_SWITCH_FG =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
private static final int USER_JOURNEY_USER_SWITCH_UI =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
private static final int USER_JOURNEY_USER_START =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
private static final int USER_JOURNEY_USER_CREATE =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
private static final int USER_JOURNEY_USER_STOP =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
@IntDef(prefix = { "USER_JOURNEY" }, value = {
USER_JOURNEY_UNKNOWN,
USER_JOURNEY_USER_SWITCH_FG,
USER_JOURNEY_USER_SWITCH_UI,
USER_JOURNEY_USER_START,
USER_JOURNEY_USER_CREATE,
USER_JOURNEY_USER_STOP
})
@interface UserJourney {}
// The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
private static final int USER_LIFECYCLE_EVENT_UNKNOWN =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
private static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
private static final int USER_LIFECYCLE_EVENT_START_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
private static final int USER_LIFECYCLE_EVENT_CREATE_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
private static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
private static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
private static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
private static final int USER_LIFECYCLE_EVENT_STOP_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
@IntDef(prefix = { "USER_LIFECYCLE_EVENT" }, value = {
USER_LIFECYCLE_EVENT_UNKNOWN,
USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_CREATE_USER,
USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
USER_LIFECYCLE_EVENT_UNLOCKING_USER,
USER_LIFECYCLE_EVENT_UNLOCKED_USER,
USER_LIFECYCLE_EVENT_STOP_USER
})
@interface UserLifecycleEvent {}
// User lifecyle event state, defined in the UserLifecycleEventOccurred atom for statsd
private static final int USER_LIFECYCLE_EVENT_STATE_BEGIN =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
private static final int USER_LIFECYCLE_EVENT_STATE_FINISH =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
private static final int USER_LIFECYCLE_EVENT_STATE_NONE =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
@IntDef(prefix = { "USER_LIFECYCLE_EVENT_STATE" }, value = {
USER_LIFECYCLE_EVENT_STATE_BEGIN,
USER_LIFECYCLE_EVENT_STATE_FINISH,
USER_LIFECYCLE_EVENT_STATE_NONE,
})
@interface UserLifecycleEventState {}
/** /**
* Maximum number of users we allow to be running at a time, including system user. * Maximum number of users we allow to be running at a time, including system user.
* *
@@ -419,13 +362,6 @@ class UserController implements Handler.Callback {
@GuardedBy("mLock") @GuardedBy("mLock")
private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>(); private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
/**
* {@link UserIdInt} to {@link UserJourneySession} mapping used for statsd logging for the
* UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
*/
@GuardedBy("mUserIdToUserJourneyMap")
private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
/** /**
* Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet- * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
* unreported user-starting events have transpired for the given user. * unreported user-starting events have transpired for the given user.
@@ -621,8 +557,9 @@ class UserController implements Handler.Callback {
// but we might immediately step into RUNNING below if the user // but we might immediately step into RUNNING below if the user
// storage is already unlocked. // storage is already unlocked.
if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) { if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED, mInjector.getUserJourneyLogger()
USER_LIFECYCLE_EVENT_STATE_NONE); .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
EVENT_STATE_NONE);
mInjector.getUserManagerInternal().setUserState(userId, uss.state); mInjector.getUserManagerInternal().setUserState(userId, uss.state);
// Do not report secondary users, runtime restarts or first boot/upgrade // Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM if (userId == UserHandle.USER_SYSTEM
@@ -694,8 +631,9 @@ class UserController implements Handler.Callback {
private boolean finishUserUnlocking(final UserState uss) { private boolean finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier(); final int userId = uss.mHandle.getIdentifier();
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId); EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER, mInjector.getUserJourneyLogger()
USER_LIFECYCLE_EVENT_STATE_BEGIN); .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
EVENT_STATE_BEGIN);
// If the user key hasn't been unlocked yet, we cannot proceed. // If the user key hasn't been unlocked yet, we cannot proceed.
if (!StorageManager.isUserKeyUnlocked(userId)) return false; if (!StorageManager.isUserKeyUnlocked(userId)) return false;
synchronized (mLock) { synchronized (mLock) {
@@ -1073,9 +1011,7 @@ class UserController implements Handler.Callback {
return; return;
} }
logUserJourneyInfo(null, getUserInfo(userId), USER_JOURNEY_USER_STOP); logUserJourneyBegin(userId, USER_JOURNEY_USER_STOP);
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
if (stopUserCallback != null) { if (stopUserCallback != null) {
uss.mStopCallbacks.add(stopUserCallback); uss.mStopCallbacks.add(stopUserCallback);
@@ -1138,9 +1074,16 @@ class UserController implements Handler.Callback {
synchronized (mLock) { synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) { if (uss.state != UserState.STATE_STOPPING) {
// Whoops, we are being started back up. Abort, abort! // Whoops, we are being started back up. Abort, abort!
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER, UserJourneySession session = mInjector.getUserJourneyLogger()
USER_LIFECYCLE_EVENT_STATE_NONE); .logUserJourneyFinishWithError(-1, getUserInfo(userId),
clearSessionId(userId); USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
if (session != null) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
} else {
mInjector.getUserJourneyLogger()
.logUserJourneyFinishWithError(-1, getUserInfo(userId),
USER_JOURNEY_USER_STOP, ERROR_CODE_INVALID_SESSION_ID);
}
return; return;
} }
uss.setState(UserState.STATE_SHUTDOWN); uss.setState(UserState.STATE_SHUTDOWN);
@@ -1247,9 +1190,11 @@ class UserController implements Handler.Callback {
mInjector.getUserManager().removeUserEvenWhenDisallowed(userId); mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
} }
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER, UserJourneySession session = mInjector.getUserJourneyLogger()
USER_LIFECYCLE_EVENT_STATE_FINISH); .logUserJourneyFinish(-1, userInfo, USER_JOURNEY_USER_STOP);
clearSessionId(userId); if (session != null) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
}
if (lockUser) { if (lockUser) {
dispatchUserLocking(userIdToLock, keyEvictedCallbacks); dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
@@ -1259,9 +1204,11 @@ class UserController implements Handler.Callback {
// which was paused while the SHUTDOWN flow of the user was in progress. // which was paused while the SHUTDOWN flow of the user was in progress.
resumePendingUserStarts(userId); resumePendingUserStarts(userId);
} else { } else {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER, UserJourneySession session = mInjector.getUserJourneyLogger()
USER_LIFECYCLE_EVENT_STATE_NONE); .finishAndClearIncompleteUserJourney(userId, USER_JOURNEY_USER_STOP);
clearSessionId(userId); if (session != null) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
}
} }
} }
@@ -3135,10 +3082,7 @@ class UserController implements Handler.Callback {
public boolean handleMessage(Message msg) { public boolean handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case START_USER_SWITCH_FG_MSG: case START_USER_SWITCH_FG_MSG:
logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1), logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_SWITCH_FG);
USER_JOURNEY_USER_SWITCH_FG);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
startUserInForeground(msg.arg1); startUserInForeground(msg.arg1);
break; break;
case REPORT_USER_SWITCH_MSG: case REPORT_USER_SWITCH_MSG:
@@ -3160,18 +3104,15 @@ class UserController implements Handler.Callback {
mInjector.batteryStatsServiceNoteEvent( mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_START, BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
Integer.toString(msg.arg1), msg.arg1); Integer.toString(msg.arg1), msg.arg1);
logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START); logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_START);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
mInjector.onUserStarting(/* userId= */ msg.arg1); mInjector.onUserStarting(/* userId= */ msg.arg1);
scheduleOnUserCompletedEvent(msg.arg1, scheduleOnUserCompletedEvent(msg.arg1,
UserCompletedEventType.EVENT_TYPE_USER_STARTING, UserCompletedEventType.EVENT_TYPE_USER_STARTING,
USER_COMPLETED_EVENT_DELAY_MS); USER_COMPLETED_EVENT_DELAY_MS);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER, mInjector.getUserJourneyLogger().logUserJourneyFinish(-1 , getUserInfo(msg.arg1),
USER_LIFECYCLE_EVENT_STATE_FINISH); USER_JOURNEY_USER_START);
clearSessionId(msg.arg1, USER_JOURNEY_USER_START);
break; break;
case USER_UNLOCK_MSG: case USER_UNLOCK_MSG:
final int userId = msg.arg1; final int userId = msg.arg1;
@@ -3180,10 +3121,11 @@ class UserController implements Handler.Callback {
FgThread.getHandler().post(() -> { FgThread.getHandler().post(() -> {
mInjector.loadUserRecents(userId); mInjector.loadUserRecents(userId);
}); });
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
USER_LIFECYCLE_EVENT_STATE_FINISH); mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER, USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_FINISH);
USER_LIFECYCLE_EVENT_STATE_BEGIN); mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_BEGIN);
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("finishUserUnlocked-" + userId); t.traceBegin("finishUserUnlocked-" + userId);
@@ -3199,9 +3141,9 @@ class UserController implements Handler.Callback {
// (No need to acquire lock to read mCurrentUserId since it is volatile.) // (No need to acquire lock to read mCurrentUserId since it is volatile.)
// TODO: Find something to wait for in the case of a profile. // TODO: Find something to wait for in the case of a profile.
mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000); mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER, mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
USER_LIFECYCLE_EVENT_STATE_FINISH); USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_FINISH);
clearSessionId(msg.arg1); // Unlocking user is not a journey no need to clear sessionId
break; break;
case USER_CURRENT_MSG: case USER_CURRENT_MSG:
mInjector.batteryStatsServiceNoteEvent( mInjector.batteryStatsServiceNoteEvent(
@@ -3224,22 +3166,24 @@ class UserController implements Handler.Callback {
break; break;
case REPORT_USER_SWITCH_COMPLETE_MSG: case REPORT_USER_SWITCH_COMPLETE_MSG:
dispatchUserSwitchComplete(msg.arg1, msg.arg2); dispatchUserSwitchComplete(msg.arg1, msg.arg2);
logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER, UserJourneySession session = mInjector.getUserJourneyLogger()
USER_LIFECYCLE_EVENT_STATE_FINISH); .logUserSwitchJourneyFinish(msg.arg1, getUserInfo(msg.arg2));
if (session != null) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
}
break; break;
case REPORT_LOCKED_BOOT_COMPLETE_MSG: case REPORT_LOCKED_BOOT_COMPLETE_MSG:
dispatchLockedBootComplete(msg.arg1); dispatchLockedBootComplete(msg.arg1);
break; break;
case START_USER_SWITCH_UI_MSG: case START_USER_SWITCH_UI_MSG:
final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj; final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second, logUserJourneyBegin(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI);
USER_JOURNEY_USER_SWITCH_UI);
logUserLifecycleEvent(fromToUserPair.second.id, USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
showUserSwitchDialog(fromToUserPair); showUserSwitchDialog(fromToUserPair);
break; break;
case CLEAR_USER_JOURNEY_SESSION_MSG: case CLEAR_USER_JOURNEY_SESSION_MSG:
logAndClearSessionId(msg.arg1); mInjector.getUserJourneyLogger()
.finishAndClearIncompleteUserJourney(msg.arg1, msg.arg2);
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, msg.obj);
break; break;
case COMPLETE_USER_SWITCH_MSG: case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1, msg.arg2); completeUserSwitch(msg.arg1, msg.arg2);
@@ -3317,123 +3261,29 @@ class UserController implements Handler.Callback {
* statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
* atom given the originating and targeting users for the journey. * atom given the originating and targeting users for the journey.
*/ */
private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) { private void logUserJourneyBegin(int targetId,
final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE); @UserJourneyLogger.UserJourney int journey) {
synchronized (mUserIdToUserJourneyMap) { UserJourneySession oldSession = mInjector.getUserJourneyLogger()
UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(target.id); .finishAndClearIncompleteUserJourney(targetId, journey);
if (userJourneySession != null) { if (oldSession != null) {
// TODO(b/157007231): Move this logic to a separate class/file.
if ((userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_UI
|| userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_FG)
&& (journey == USER_JOURNEY_USER_START
|| journey == USER_JOURNEY_USER_STOP)) {
/*
* There is already a user switch journey, and a user start or stop journey for
* the same target user received. New journey is most likely a part of user
* switch journey so no need to create a new journey.
*/
if (DEBUG_MU) {
Slogf.d(TAG, journey + " not logged as it is expected to be part of "
+ userJourneySession.mJourney);
}
return;
}
/*
* Possible reasons for this condition to be true:
* - A user switch journey is received while another user switch journey is in
* process for the same user.
* - A user switch journey is received while user start journey is in process for
* the same user.
* - A user start journey is received while another user start journey is in process
* for the same user.
* In all cases potentially an incomplete, timed-out session or multiple
* simultaneous requests. It is not possible to keep track of multiple sessions for
* the same user, so previous session is abandoned.
*/
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
userJourneySession.mSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN,
USER_LIFECYCLE_EVENT_STATE_NONE);
}
if (DEBUG_MU) { if (DEBUG_MU) {
Slogf.d(TAG, Slogf.d(TAG,
"Starting a new journey: " + journey + " with session id: " + newSessionId); "Starting a new journey: " + journey + " with session id: "
+ oldSession);
} }
userJourneySession = new UserJourneySession(newSessionId, journey);
mUserIdToUserJourneyMap.put(target.id, userJourneySession);
/* /*
* User lifecyle journey would be complete when {@code #clearSessionId} is called after * User lifecycle journey would be complete when {@code #clearSessionId} is called
* the last expected lifecycle event for the journey. It may be possible that the last * after the last expected lifecycle event for the journey. It may be possible that
* event is not called, e.g., user not unlocked after user switching. In such cases user * the last event is not called, e.g., user not unlocked after user switching. In such
* journey is cleared after {@link USER_JOURNEY_TIMEOUT}. * cases user journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
*/ */
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG); mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, oldSession);
mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS);
} }
UserJourneySession newSession = mInjector.getUserJourneyLogger()
.logUserJourneyBegin(targetId, journey);
mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
targetId, /* arg2= */ journey, newSession), USER_JOURNEY_TIMEOUT_MS);
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId,
journey, origin != null ? origin.id : -1,
target.id, UserManager.getUserTypeForStatsd(target.userType), target.flags);
}
/**
* statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
* atom.
*/
private void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
@UserLifecycleEventState int eventState) {
final long sessionId;
synchronized (mUserIdToUserJourneyMap) {
final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
if (userJourneySession == null || userJourneySession.mSessionId == INVALID_SESSION_ID) {
Slogf.w(TAG, "UserLifecycleEvent " + event
+ " received without an active userJourneySession.");
return;
}
sessionId = userJourneySession.mSessionId;
}
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
event, eventState);
}
/**
* Clears the {@link UserJourneySession} for a given {@link UserIdInt} and {@link UserJourney}.
*/
private void clearSessionId(@UserIdInt int userId, @UserJourney int journey) {
synchronized (mUserIdToUserJourneyMap) {
final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
if (userJourneySession != null && userJourneySession.mJourney == journey) {
clearSessionId(userId);
}
}
}
/**
* Clears the {@link UserJourneySession} for a given {@link UserIdInt}.
*/
private void clearSessionId(@UserIdInt int userId) {
synchronized (mUserIdToUserJourneyMap) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
mUserIdToUserJourneyMap.delete(userId);
}
}
/**
* Log a final event of the {@link UserJourneySession} and clear it.
*/
private void logAndClearSessionId(@UserIdInt int userId) {
synchronized (mUserIdToUserJourneyMap) {
final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
if (userJourneySession != null) {
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
userJourneySession.mSessionId, userId, USER_LIFECYCLE_EVENT_UNKNOWN,
USER_LIFECYCLE_EVENT_STATE_NONE);
}
clearSessionId(userId);
}
} }
private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions( private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@@ -3471,23 +3321,6 @@ class UserController implements Handler.Callback {
return mLastUserUnlockingUptime; return mLastUserUnlockingUptime;
} }
/**
* Helper class to store user journey and session id.
*
* <p> User journey tracks a chain of user lifecycle events occurring during different user
* activities such as user start, user switch, and user creation.
*/
// TODO(b/157007231): Move this class and user journey tracking logic to a separate file.
private static class UserJourneySession {
final long mSessionId;
@UserJourney final int mJourney;
UserJourneySession(long sessionId, @UserJourney int journey) {
mJourney = journey;
mSessionId = sessionId;
}
}
private static class UserProgressListener extends IProgressListener.Stub { private static class UserProgressListener extends IProgressListener.Stub {
private volatile long mUnlockStarted; private volatile long mUnlockStarted;
@Override @Override
@@ -3562,6 +3395,10 @@ class UserController implements Handler.Callback {
return new Handler(mService.mUiHandler.getLooper(), callback); return new Handler(mService.mUiHandler.getLooper(), callback);
} }
protected UserJourneyLogger getUserJourneyLogger() {
return getUserManager().getUserJourneyLogger();
}
protected Context getContext() { protected Context getContext() {
return mService.mContext; return mService.mContext;
} }

View File

@@ -0,0 +1,552 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import static android.os.UserManager.USER_TYPE_FULL_DEMO;
import static android.os.UserManager.USER_TYPE_FULL_GUEST;
import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
import static com.android.internal.util.FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import java.util.concurrent.ThreadLocalRandom;
/**
* This class is logging User Lifecycle statsd events and synchronise User Lifecycle Journeys
* by making sure all events are called in correct order and errors are reported in case of
* unexpected journeys. This class also makes sure that all user sub-journeys are logged so
* for example User Switch also log User Start Journey.
*/
public class UserJourneyLogger {
public static final int ERROR_CODE_INVALID_SESSION_ID = 0;
public static final int ERROR_CODE_UNSPECIFIED = -1;
/*
* Possible reasons for ERROR_CODE_INCOMPLETE_OR_TIMEOUT to occur:
* - A user switch journey is received while another user switch journey is in
* process for the same user.
* - A user switch journey is received while user start journey is in process for
* the same user.
* - A user start journey is received while another user start journey is in process
* for the same user.
* In all cases potentially an incomplete, timed-out session or multiple
* simultaneous requests. It is not possible to keep track of multiple sessions for
* the same user, so previous session is abandoned.
*/
public static final int ERROR_CODE_INCOMPLETE_OR_TIMEOUT = 2;
public static final int ERROR_CODE_ABORTED = 3;
public static final int ERROR_CODE_NULL_USER_INFO = 4;
public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5;
public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6;
@IntDef(prefix = {"ERROR_CODE"}, value = {
ERROR_CODE_UNSPECIFIED,
ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
ERROR_CODE_ABORTED,
ERROR_CODE_NULL_USER_INFO,
ERROR_CODE_USER_ALREADY_AN_ADMIN,
ERROR_CODE_USER_IS_NOT_AN_ADMIN,
ERROR_CODE_INVALID_SESSION_ID
})
public @interface UserJourneyErrorCode {
}
// The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
public static final int USER_JOURNEY_UNKNOWN =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
public static final int USER_JOURNEY_USER_SWITCH_FG =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
public static final int USER_JOURNEY_USER_SWITCH_UI =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
public static final int USER_JOURNEY_USER_START =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
public static final int USER_JOURNEY_USER_CREATE =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
public static final int USER_JOURNEY_USER_STOP =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
public static final int USER_JOURNEY_USER_REMOVE =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE;
public static final int USER_JOURNEY_GRANT_ADMIN =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN;
public static final int USER_JOURNEY_REVOKE_ADMIN =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN;
@IntDef(prefix = {"USER_JOURNEY"}, value = {
USER_JOURNEY_UNKNOWN,
USER_JOURNEY_USER_SWITCH_FG,
USER_JOURNEY_USER_SWITCH_UI,
USER_JOURNEY_USER_START,
USER_JOURNEY_USER_STOP,
USER_JOURNEY_USER_CREATE,
USER_JOURNEY_USER_REMOVE,
USER_JOURNEY_GRANT_ADMIN,
USER_JOURNEY_REVOKE_ADMIN
})
public @interface UserJourney {
}
// The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
public static final int USER_LIFECYCLE_EVENT_UNKNOWN =
USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
public static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
public static final int USER_LIFECYCLE_EVENT_START_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
public static final int USER_LIFECYCLE_EVENT_CREATE_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
public static final int USER_LIFECYCLE_EVENT_REMOVE_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
public static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
public static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
public static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
public static final int USER_LIFECYCLE_EVENT_STOP_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
public static final int USER_LIFECYCLE_EVENT_GRANT_ADMIN =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
public static final int USER_LIFECYCLE_EVENT_REVOKE_ADMIN =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
@IntDef(prefix = {"USER_LIFECYCLE_EVENT"}, value = {
USER_LIFECYCLE_EVENT_UNKNOWN,
USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_CREATE_USER,
USER_LIFECYCLE_EVENT_REMOVE_USER,
USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
USER_LIFECYCLE_EVENT_UNLOCKING_USER,
USER_LIFECYCLE_EVENT_UNLOCKED_USER,
USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_GRANT_ADMIN,
USER_LIFECYCLE_EVENT_REVOKE_ADMIN
})
public @interface UserLifecycleEvent {
}
// User lifecycle event state, defined in the UserLifecycleEventOccurred atom for statsd
public static final int EVENT_STATE_BEGIN =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
public static final int EVENT_STATE_FINISH =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
public static final int EVENT_STATE_NONE =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
public static final int EVENT_STATE_CANCEL =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__CANCEL;
public static final int EVENT_STATE_ERROR =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR;
@IntDef(prefix = {"EVENT_STATE"}, value = {
EVENT_STATE_BEGIN,
EVENT_STATE_FINISH,
EVENT_STATE_NONE,
EVENT_STATE_CANCEL,
EVENT_STATE_ERROR,
})
public @interface UserLifecycleEventState {
}
private static final int USER_ID_KEY_MULTIPLICATION = 100;
private final Object mLock = new Object();
/**
* {@link UserIdInt} and {@link UserJourney} to {@link UserJourneySession} mapping used for
* statsd logging for the UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
*/
@GuardedBy("mLock")
private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
/**
* Returns event equivalent of given journey
*/
@UserLifecycleEvent
private static int journeyToEvent(@UserJourney int journey) {
switch (journey) {
case USER_JOURNEY_USER_SWITCH_UI:
case USER_JOURNEY_USER_SWITCH_FG:
return USER_LIFECYCLE_EVENT_SWITCH_USER;
case USER_JOURNEY_USER_START:
return USER_LIFECYCLE_EVENT_START_USER;
case USER_JOURNEY_USER_CREATE:
return USER_LIFECYCLE_EVENT_CREATE_USER;
case USER_JOURNEY_USER_STOP:
return USER_LIFECYCLE_EVENT_STOP_USER;
case USER_JOURNEY_USER_REMOVE:
return USER_LIFECYCLE_EVENT_REMOVE_USER;
case USER_JOURNEY_GRANT_ADMIN:
return USER_LIFECYCLE_EVENT_GRANT_ADMIN;
case USER_JOURNEY_REVOKE_ADMIN:
return USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
default:
return USER_LIFECYCLE_EVENT_UNKNOWN;
}
}
/**
* Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to
* the user type.
* Changes to this method require changes in CTS file
* com.android.cts.packagemanager.stats.device.UserInfoUtil
* which is duplicate for CTS tests purposes.
*/
public static int getUserTypeForStatsd(@NonNull String userType) {
switch (userType) {
case USER_TYPE_FULL_SYSTEM:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
case USER_TYPE_FULL_SECONDARY:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
case USER_TYPE_FULL_GUEST:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
case USER_TYPE_FULL_DEMO:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
case USER_TYPE_FULL_RESTRICTED:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
case USER_TYPE_PROFILE_MANAGED:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
case USER_TYPE_SYSTEM_HEADLESS:
return FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
case USER_TYPE_PROFILE_CLONE:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
default:
return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
}
}
/**
* Map error code to the event finish state.
*/
@UserLifecycleEventState
private static int errorToFinishState(@UserJourneyErrorCode int errorCode) {
switch (errorCode) {
case ERROR_CODE_ABORTED:
return EVENT_STATE_CANCEL;
case ERROR_CODE_UNSPECIFIED:
return EVENT_STATE_FINISH;
default:
return EVENT_STATE_ERROR;
}
}
/**
* Simply logging USER_LIFECYCLE_JOURNEY_REPORTED if session exists.
* If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
*/
@VisibleForTesting
public void logUserLifecycleJourneyReported(@Nullable UserJourneySession session,
@UserJourney int journey, @UserIdInt int originalUserId, @UserIdInt int targetUserId,
int userType, int userFlags, @UserJourneyErrorCode int errorCode) {
if (session == null) {
writeUserLifecycleJourneyReported(-1, journey, originalUserId, targetUserId,
userType, userFlags, ERROR_CODE_INVALID_SESSION_ID);
} else {
writeUserLifecycleJourneyReported(
session.mSessionId, journey, originalUserId, targetUserId, userType, userFlags,
errorCode);
}
}
/**
* Helper method for spy testing
*/
@VisibleForTesting
public void writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId,
int targetUserId, int userType, int userFlags, int errorCode) {
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED,
sessionId, journey, originalUserId, targetUserId, userType, userFlags,
errorCode);
}
/**
* Simply logging USER_LIFECYCLE_EVENT_OCCURRED if session exists.
* If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
* and EVENT_STATE_ERROR
*/
@VisibleForTesting
public void logUserLifecycleEventOccurred(UserJourneySession session,
@UserIdInt int targetUserId, @UserLifecycleEvent int event,
@UserLifecycleEventState int state, @UserJourneyErrorCode int errorCode) {
if (session == null) {
writeUserLifecycleEventOccurred(-1, targetUserId, event,
EVENT_STATE_ERROR, ERROR_CODE_INVALID_SESSION_ID);
} else {
writeUserLifecycleEventOccurred(session.mSessionId, targetUserId, event, state,
errorCode);
}
}
/**
* Helper method for spy testing
*/
@VisibleForTesting
public void writeUserLifecycleEventOccurred(long sessionId, int userId, int event, int state,
int errorCode) {
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
sessionId, userId, event, state, errorCode);
}
/**
* statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
* atom. It finds the user journey session for target user id and logs it as that journey.
*/
public void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
@UserLifecycleEventState int eventState) {
final UserJourneySession userJourneySession = findUserJourneySession(userId);
logUserLifecycleEventOccurred(userJourneySession, userId,
event, eventState, UserJourneyLogger.ERROR_CODE_UNSPECIFIED);
}
/**
* Returns first user session from mUserIdToUserJourneyMap for given user id,
* or null if user id was not found in mUserIdToUserJourneyMap.
*/
private @Nullable UserJourneySession findUserJourneySession(@UserIdInt int userId) {
synchronized (mLock) {
final int keyMapSize = mUserIdToUserJourneyMap.size();
for (int i = 0; i < keyMapSize; i++) {
int key = mUserIdToUserJourneyMap.keyAt(i);
if (key / USER_ID_KEY_MULTIPLICATION == userId) {
return mUserIdToUserJourneyMap.get(key);
}
}
}
return null;
}
/**
* Returns unique id for user and journey. For example if user id = 11 and journey = 7
* then unique key = 11 * 100 + 7 = 1107
*/
private int getUserJourneyKey(@UserIdInt int targetUserId, @UserJourney int journey) {
// We leave 99 for user journeys ids.
return (targetUserId * USER_ID_KEY_MULTIPLICATION) + journey;
}
/**
* Special use case when user journey incomplete or timeout and current user is unclear
*/
@VisibleForTesting
public UserJourneySession finishAndClearIncompleteUserJourney(@UserIdInt int targetUserId,
@UserJourney int journey) {
synchronized (mLock) {
final int key = getUserJourneyKey(targetUserId, journey);
final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
if (userJourneySession != null) {
logUserLifecycleEventOccurred(
userJourneySession,
targetUserId,
journeyToEvent(userJourneySession.mJourney),
EVENT_STATE_ERROR,
UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
logUserLifecycleJourneyReported(
userJourneySession,
journey,
/* originalUserId= */ -1,
targetUserId,
getUserTypeForStatsd(""), -1,
ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
mUserIdToUserJourneyMap.remove(key);
return userJourneySession;
}
}
return null;
}
/**
* Log user journey event and report finishing without error
*/
public UserJourneySession logUserJourneyFinish(@UserIdInt int originalUserId,
UserInfo targetUser, @UserJourney int journey) {
return logUserJourneyFinishWithError(originalUserId, targetUser, journey,
ERROR_CODE_UNSPECIFIED);
}
/**
* Special case when it is unknown which user switch journey was used and checking both
*/
@VisibleForTesting
public UserJourneySession logUserSwitchJourneyFinish(@UserIdInt int originalUserId,
UserInfo targetUser) {
synchronized (mLock) {
final int key_fg = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_FG);
final int key_ui = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_UI);
if (mUserIdToUserJourneyMap.contains(key_fg)) {
return logUserJourneyFinish(originalUserId, targetUser,
USER_JOURNEY_USER_SWITCH_FG);
}
if (mUserIdToUserJourneyMap.contains(key_ui)) {
return logUserJourneyFinish(originalUserId, targetUser,
USER_JOURNEY_USER_SWITCH_UI);
}
return null;
}
}
/**
* Log user journey event and report finishing with error
*/
public UserJourneySession logUserJourneyFinishWithError(@UserIdInt int originalUserId,
UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
synchronized (mLock) {
final int state = errorToFinishState(errorCode);
final int key = getUserJourneyKey(targetUser.id, journey);
final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
if (userJourneySession != null) {
logUserLifecycleEventOccurred(
userJourneySession, targetUser.id,
journeyToEvent(userJourneySession.mJourney),
state,
errorCode);
logUserLifecycleJourneyReported(
userJourneySession,
journey, originalUserId, targetUser.id,
getUserTypeForStatsd(targetUser.userType),
targetUser.flags,
errorCode);
mUserIdToUserJourneyMap.remove(key);
return userJourneySession;
}
}
return null;
}
/**
* Log event and report finish when user is null. This is edge case when UserInfo
* can not be passed because it is null, therefore all information are passed as arguments.
*/
public UserJourneySession logNullUserJourneyError(@UserJourney int journey,
@UserIdInt int currentUserId, @UserIdInt int targetUserId, String targetUserType,
int targetUserFlags) {
synchronized (mLock) {
final int key = getUserJourneyKey(targetUserId, journey);
final UserJourneySession session = mUserIdToUserJourneyMap.get(key);
logUserLifecycleEventOccurred(
session, targetUserId, journeyToEvent(journey),
EVENT_STATE_ERROR,
ERROR_CODE_NULL_USER_INFO);
logUserLifecycleJourneyReported(
session, journey, currentUserId, targetUserId,
getUserTypeForStatsd(targetUserType), targetUserFlags,
ERROR_CODE_NULL_USER_INFO);
mUserIdToUserJourneyMap.remove(key);
return session;
}
}
/**
* Log for user creation finish event and report. This is edge case when target user id is
* different in begin event and finish event as it is unknown what is user id
* until it has been created.
*/
public UserJourneySession logUserCreateJourneyFinish(@UserIdInt int originalUserId,
UserInfo targetUser) {
synchronized (mLock) {
// we do not know user id until we create new user which is why we use -1
// as user id to create and find session, but we log correct id.
final int key = getUserJourneyKey(-1, USER_JOURNEY_USER_CREATE);
final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
if (userJourneySession != null) {
logUserLifecycleEventOccurred(
userJourneySession, targetUser.id,
USER_LIFECYCLE_EVENT_CREATE_USER,
EVENT_STATE_FINISH,
ERROR_CODE_UNSPECIFIED);
logUserLifecycleJourneyReported(
userJourneySession,
USER_JOURNEY_USER_CREATE, originalUserId, targetUser.id,
getUserTypeForStatsd(targetUser.userType),
targetUser.flags,
ERROR_CODE_UNSPECIFIED);
mUserIdToUserJourneyMap.remove(key);
return userJourneySession;
}
}
return null;
}
/**
* Adds new UserJourneySession to mUserIdToUserJourneyMap and log UserJourneyEvent Begin state
*/
public UserJourneySession logUserJourneyBegin(@UserIdInt int targetId,
@UserJourney int journey) {
final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
synchronized (mLock) {
final int key = getUserJourneyKey(targetId, journey);
final UserJourneySession userJourneySession =
new UserJourneySession(newSessionId, journey);
mUserIdToUserJourneyMap.append(key, userJourneySession);
logUserLifecycleEventOccurred(
userJourneySession, targetId,
journeyToEvent(userJourneySession.mJourney),
EVENT_STATE_BEGIN,
ERROR_CODE_UNSPECIFIED);
return userJourneySession;
}
}
/**
* Helper class to store user journey and session id.
*
* <p> User journey tracks a chain of user lifecycle events occurring during different user
* activities such as user start, user switch, and user creation.
*/
public static class UserJourneySession {
public final long mSessionId;
@UserJourney
public final int mJourney;
@VisibleForTesting
public UserJourneySession(long sessionId, @UserJourney int journey) {
mJourney = journey;
mSessionId = sessionId;
}
}
}

View File

@@ -23,6 +23,15 @@ import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN; import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
import android.Manifest; import android.Manifest;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
@@ -162,7 +171,6 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -519,6 +527,8 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserLifecycleListeners") @GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>(); private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
private final UserJourneyLogger mUserJourneyLogger = new UserJourneyLogger();
private final LockPatternUtils mLockPatternUtils; private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK = private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -1580,45 +1590,56 @@ public class UserManagerService extends IUserManager.Stub {
@Override @Override
public void setUserAdmin(@UserIdInt int userId) { public void setUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("set user admin"); checkManageUserAndAcrossUsersFullPermission("set user admin");
final long sessionId = logGrantAdminJourneyBegin(userId); mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
UserInfo info; UserInfo info;
synchronized (mPackagesLock) { synchronized (mPackagesLock) {
synchronized (mUsersLock) { synchronized (mUsersLock) {
info = getUserInfoLU(userId); info = getUserInfoLU(userId);
} }
if (info == null || info.isAdmin()) { if (info == null) {
// Exit if no user found with that id, or the user is already an Admin. // Exit if no user found with that id,
logUserJourneyError(sessionId, mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN, getCurrentUserId(), userId, /* userType */ "", /* userFlags */ -1);
userId); return;
} else if (info.isAdmin()) {
// Exit if the user is already an Admin.
mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_USER_ALREADY_AN_ADMIN);
return; return;
} }
info.flags ^= UserInfo.FLAG_ADMIN; info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(getUserDataLU(info.id)); writeUserLP(getUserDataLU(info.id));
} }
logGrantAdminJourneyFinish(sessionId, userId, info.userType, info.flags); mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_UNSPECIFIED);
} }
@Override @Override
public void revokeUserAdmin(@UserIdInt int userId) { public void revokeUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("revoke admin privileges"); checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
final long sessionId = logRevokeAdminJourneyBegin(userId); mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
UserData user; UserData user;
synchronized (mPackagesLock) { synchronized (mPackagesLock) {
synchronized (mUsersLock) { synchronized (mUsersLock) {
user = getUserDataLU(userId); user = getUserDataLU(userId);
if (user == null || !user.info.isAdmin()) { if (user == null) {
// Exit if no user found with that id, or the user is not an Admin. // Exit if no user found with that id
logUserJourneyError(sessionId, FrameworkStatsLog mUserJourneyLogger.logNullUserJourneyError(
.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN, USER_JOURNEY_REVOKE_ADMIN,
userId); getCurrentUserId(), userId, "", -1);
return;
} else if (!user.info.isAdmin()) {
// Exit if no user is not an Admin.
mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN);
return; return;
} }
user.info.flags ^= UserInfo.FLAG_ADMIN; user.info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(user); writeUserLP(user);
} }
} }
logRevokeAdminJourneyFinish(sessionId, userId, user.info.userType, user.info.flags); mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_UNSPECIFIED);
} }
/** /**
@@ -4700,16 +4721,20 @@ public class UserManagerService extends IUserManager.Stub {
final int noneUserId = -1; final int noneUserId = -1;
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("createUser-" + flags); t.traceBegin("createUser-" + flags);
final long sessionId = logUserCreateJourneyBegin(noneUserId); mUserJourneyLogger.logUserJourneyBegin(noneUserId, USER_JOURNEY_USER_CREATE);
UserInfo newUser = null; UserInfo newUser = null;
try { try {
newUser = createUserInternalUncheckedNoTracing(name, userType, flags, parentId, newUser = createUserInternalUncheckedNoTracing(name, userType, flags, parentId,
preCreate, disallowedPackages, t, token); preCreate, disallowedPackages, t, token);
return newUser; return newUser;
} finally { } finally {
logUserCreateJourneyFinish(sessionId, if (newUser != null) {
newUser != null ? newUser.id : noneUserId, userType, flags, mUserJourneyLogger.logUserCreateJourneyFinish(getCurrentUserId(), newUser);
newUser != null); } else {
mUserJourneyLogger.logNullUserJourneyError(
USER_JOURNEY_USER_CREATE,
getCurrentUserId(), noneUserId, userType, flags);
}
t.traceEnd(); t.traceEnd();
} }
} }
@@ -5198,137 +5223,6 @@ public class UserManagerService extends IUserManager.Stub {
&& !userTypeDetails.getName().equals(UserManager.USER_TYPE_FULL_RESTRICTED); && !userTypeDetails.getName().equals(UserManager.USER_TYPE_FULL_RESTRICTED);
} }
private long logUserCreateJourneyBegin(@UserIdInt int userId) {
return logUserJourneyBegin(
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
userId);
}
private void logUserCreateJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
@UserInfoFlag int flags, boolean finish) {
logUserJourneyFinish(sessionId,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
userId, userType, flags, finish);
}
private long logUserRemoveJourneyBegin(@UserIdInt int userId) {
return logUserJourneyBegin(
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
userId);
}
private void logUserRemoveJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
@UserInfoFlag int flags, boolean finish) {
logUserJourneyFinish(sessionId,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
userId, userType, flags, finish);
}
private long logGrantAdminJourneyBegin(@UserIdInt int userId) {
return logUserJourneyBegin(
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
userId);
}
private void logGrantAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
@UserInfoFlag int flags) {
logUserJourneyFinish(sessionId,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
userId, userType, flags, true);
}
private long logRevokeAdminJourneyBegin(@UserIdInt int userId) {
return logUserJourneyBegin(
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
userId);
}
private void logRevokeAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
@UserInfoFlag int flags) {
logUserJourneyFinish(sessionId,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
userId, userType, flags, true);
}
private void logUserJourneyFinish(long sessionId, int journey, @UserIdInt int userId,
String userType, @UserInfoFlag int flags, boolean finish) {
// log the journey atom with the user metadata
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
journey, /* origin_user= */ getCurrentUserId(), userId,
UserManager.getUserTypeForStatsd(userType), flags);
int event;
switch (journey) {
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
break;
default:
throw new IllegalArgumentException("Journey " + journey + " not expected.");
}
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
event,
finish ? FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH
: FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE);
}
private long logUserJourneyBegin(int journey, @UserIdInt int userId) {
final long sessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
// log the event atom to indicate the event start
int event;
switch (journey) {
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
break;
default:
throw new IllegalArgumentException("Journey " + journey + " not expected.");
}
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN);
return sessionId;
}
private void logUserJourneyError(long sessionId, int journey, @UserIdInt int userId) {
// log the journey atom with the user metadata
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
journey, /* origin_user= */ getCurrentUserId(), userId);
int event;
switch (journey) {
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
break;
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
break;
default:
throw new IllegalArgumentException("Journey " + journey + " not expected.");
}
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR);
}
/** Register callbacks for statsd pulled atoms. */ /** Register callbacks for statsd pulled atoms. */
private void registerStatsCallbacks() { private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class); final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -5352,7 +5246,8 @@ public class UserManagerService extends IUserManager.Stub {
if (size > 1) { if (size > 1) {
for (int idx = 0; idx < size; idx++) { for (int idx = 0; idx < size; idx++) {
final UserInfo user = users.get(idx); final UserInfo user = users.get(idx);
final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType); final int userTypeStandard = mUserJourneyLogger
.getUserTypeForStatsd(user.userType);
final String userTypeCustom = (userTypeStandard == FrameworkStatsLog final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
? ?
@@ -5635,7 +5530,7 @@ public class UserManagerService extends IUserManager.Stub {
writeUserLP(userData); writeUserLP(userData);
} }
final long sessionId = logUserRemoveJourneyBegin(userId); mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_USER_REMOVE);
try { try {
mAppOpsService.removeUser(userId); mAppOpsService.removeUser(userId);
@@ -5657,13 +5552,17 @@ public class UserManagerService extends IUserManager.Stub {
@Override @Override
public void userStopped(int userIdParam) { public void userStopped(int userIdParam) {
finishRemoveUser(userIdParam); finishRemoveUser(userIdParam);
logUserRemoveJourneyFinish(sessionId, userIdParam, int originUserId = UserManagerService.this.getCurrentUserId();
userData.info.userType, userData.info.flags, true); mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
userData.info, USER_JOURNEY_USER_REMOVE,
ERROR_CODE_UNSPECIFIED);
} }
@Override @Override
public void userStopAborted(int userIdParam) { public void userStopAborted(int userIdParam) {
logUserRemoveJourneyFinish(sessionId, userIdParam, int originUserId = UserManagerService.this.getCurrentUserId();
userData.info.userType, userData.info.flags, false); mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
userData.info, USER_JOURNEY_USER_REMOVE,
ERROR_CODE_ABORTED);
} }
}); });
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -7297,9 +7196,9 @@ public class UserManagerService extends IUserManager.Stub {
final UserInfo userInfo = getUserInfo(userIds[i]); final UserInfo userInfo = getUserInfo(userIds[i]);
if (userInfo == null) { if (userInfo == null) {
// Not possible because the input user ids should all be valid // Not possible because the input user ids should all be valid
userTypes[i] = UserManager.getUserTypeForStatsd(""); userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd("");
} else { } else {
userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType); userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
} }
} }
return userTypes; return userTypes;
@@ -7536,4 +7435,11 @@ public class UserManagerService extends IUserManager.Stub {
.getBoolean(R.bool.config_canSwitchToHeadlessSystemUser); .getBoolean(R.bool.config_canSwitchToHeadlessSystemUser);
} }
/**
* Returns instance of {@link com.android.server.pm.UserJourneyLogger}.
*/
public UserJourneyLogger getUserJourneyLogger() {
return mUserJourneyLogger;
}
} }

View File

@@ -100,6 +100,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread; import com.android.server.FgThread;
import com.android.server.SystemService; import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService; import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowManagerService;
@@ -1074,6 +1075,8 @@ public class UserControllerTest {
private final KeyguardManager mKeyguardManagerMock; private final KeyguardManager mKeyguardManagerMock;
private final LockPatternUtils mLockPatternUtilsMock; private final LockPatternUtils mLockPatternUtilsMock;
private final UserJourneyLogger mUserJourneyLoggerMock;
private final Context mCtx; private final Context mCtx;
TestInjector(Context ctx) { TestInjector(Context ctx) {
@@ -1090,6 +1093,7 @@ public class UserControllerTest {
mKeyguardManagerMock = mock(KeyguardManager.class); mKeyguardManagerMock = mock(KeyguardManager.class);
when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
mLockPatternUtilsMock = mock(LockPatternUtils.class); mLockPatternUtilsMock = mock(LockPatternUtils.class);
mUserJourneyLoggerMock = mock(UserJourneyLogger.class);
} }
@Override @Override
@@ -1220,6 +1224,11 @@ public class UserControllerTest {
void onSystemUserVisibilityChanged(boolean visible) { void onSystemUserVisibilityChanged(boolean visible) {
Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")"); Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
} }
@Override
protected UserJourneyLogger getUserJourneyLogger() {
return mUserJourneyLoggerMock;
}
} }
private static class TestHandler extends Handler { private static class TestHandler extends Handler {

View File

@@ -0,0 +1,580 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_NULL_USER_INFO;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_CANCEL;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_ERROR;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_CREATE_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REMOVE_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_START_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_STOP_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.pm.UserInfo;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.FrameworkStatsLog;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class UserJourneyLoggerTest {
public static final int FULL_USER_ADMIN_FLAG = 0x00000402;
private UserJourneyLogger mUserJourneyLogger;
@Before
public void setup() throws Exception {
mUserJourneyLogger = spy(new UserJourneyLogger());
}
@Test
public void testUserStartLifecycleJourneyReported() {
final UserLifecycleJourneyReportedCaptor report1 = new UserLifecycleJourneyReportedCaptor();
final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
.UserJourneySession(10, USER_JOURNEY_USER_START);
report1.captureLogAndAssert(mUserJourneyLogger, session,
USER_JOURNEY_USER_START, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY, 1,
ERROR_CODE_UNSPECIFIED);
}
@Test
public void testUserLifecycleEventOccurred() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
.UserJourneySession(10, USER_JOURNEY_USER_START);
report1.captureLogAndAssert(mUserJourneyLogger, session, 0,
USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED);
}
@Test
public void testLogUserLifecycleEvent() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
mUserJourneyLogger.logUserLifecycleEvent(10, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
EVENT_STATE_NONE);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
EVENT_STATE_NONE, ERROR_CODE_UNSPECIFIED, 2);
}
@Test
public void testCreateUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(-1, USER_JOURNEY_USER_CREATE);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, -1,
USER_LIFECYCLE_EVENT_CREATE_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserCreateJourneyFinish(0, targetUser);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_CREATE_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_USER_CREATE, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
}
@Test
public void testRemoveUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_REMOVE);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_REMOVE_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
USER_JOURNEY_USER_REMOVE);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_REMOVE_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_USER_REMOVE, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
}
@Test
public void testStartUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_USER_START, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
}
@Test
public void testStopUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_USER_STOP, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
}
@Test
public void testAbortStopUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinishWithError(-1, targetUser,
USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER,
EVENT_STATE_CANCEL, ERROR_CODE_ABORTED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_USER_STOP, -1, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_ABORTED, 1);
}
@Test
public void testIncompleteStopUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
mUserJourneyLogger.finishAndClearIncompleteUserJourney(10, USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER,
EVENT_STATE_ERROR,
ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_USER_STOP, -1, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN,
-1, // information about user are incomplete
ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 1);
}
@Test
public void testGrantAdminUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
USER_JOURNEY_GRANT_ADMIN);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_GRANT_ADMIN, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
}
@Test
public void testNullUserErrorGrantAdminUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
0, 10, "", -1);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
EVENT_STATE_ERROR, ERROR_CODE_NULL_USER_INFO, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
USER_JOURNEY_GRANT_ADMIN, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN,
-1, ERROR_CODE_NULL_USER_INFO, 1);
}
@Test
public void testRevokeAdminUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(10, UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user", UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN, 0, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000400, ERROR_CODE_UNSPECIFIED, 1);
}
@Test
public void testSwitchFGUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
.logUserJourneyBegin(11, USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(11, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
USER_JOURNEY_USER_START, 10, 11,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG, 10, 11,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 2);
}
@Test
public void testSwitchUIUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
.logUserJourneyBegin(11, USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
UserInfo targetUser = new UserInfo(11, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
USER_JOURNEY_USER_START, 10, 11,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 1);
mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
EVENT_STATE_FINISH,
ERROR_CODE_UNSPECIFIED, 4);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 2);
}
@Test
public void testSwitchWithStopUIUserJourney() {
final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
// BEGIN USER SWITCH
final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
.logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
// BEGIN USER STOP
final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
.logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
// BEGIN USER START
UserJourneyLogger.UserJourneySession session3 = mUserJourneyLogger
.logUserJourneyBegin(11, USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 3);
// FINISH USER STOP
final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
final UserInfo targetUser = new UserInfo(10, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(-1, targetUser,
USER_JOURNEY_USER_STOP);
report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
USER_LIFECYCLE_EVENT_STOP_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
USER_JOURNEY_USER_STOP, -1, 10,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 1);
// FINISH USER START
final UserInfo targetUser2 = new UserInfo(11, "test target user",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
mUserJourneyLogger.logUserJourneyFinish(10, targetUser2,
USER_JOURNEY_USER_START);
report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
USER_LIFECYCLE_EVENT_START_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 5);
report2.captureAndAssert(mUserJourneyLogger, session3.mSessionId,
USER_JOURNEY_USER_START, 10, 11,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 2);
// FINISH USER SWITCH
mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser2);
report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 6);
report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
0x00000402, ERROR_CODE_UNSPECIFIED, 3);
}
static class UserLifecycleJourneyReportedCaptor {
ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
ArgumentCaptor<Integer> mJourney = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mOriginalUserId = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mUserType = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mUserFlags = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
long sessionId, int journey, int originalUserId,
int targetUserId, int userType, int userFlags, int errorCode, int times) {
verify(mUserJourneyLogger, times(times))
.writeUserLifecycleJourneyReported(mSessionId.capture(),
mJourney.capture(),
mOriginalUserId.capture(),
mTargetUserId.capture(),
mUserType.capture(),
mUserFlags.capture(),
mErrorCode.capture());
assertThat(mSessionId.getValue()).isEqualTo(sessionId);
assertThat(mJourney.getValue()).isEqualTo(journey);
assertThat(mOriginalUserId.getValue()).isEqualTo(originalUserId);
assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
assertThat(mUserType.getValue()).isEqualTo(userType);
assertThat(mUserFlags.getValue()).isEqualTo(userFlags);
assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
}
public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
UserJourneyLogger.UserJourneySession session, int journey, int originalUserId,
int targetUserId, int userType, int userFlags, int errorCode) {
mUserJourneyLogger.logUserLifecycleJourneyReported(session, journey, originalUserId,
targetUserId, userType, userFlags, errorCode);
captureAndAssert(mUserJourneyLogger, session.mSessionId, journey, originalUserId,
targetUserId, userType, userFlags, errorCode, 1);
}
}
static class UserLifecycleEventOccurredCaptor {
ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mEvent = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mStste = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
long sessionId, int targetUserId, int event, int state, int errorCode, int times) {
verify(mUserJourneyLogger, times(times))
.writeUserLifecycleEventOccurred(mSessionId.capture(),
mTargetUserId.capture(),
mEvent.capture(),
mStste.capture(),
mErrorCode.capture());
assertThat(mSessionId.getValue()).isEqualTo(sessionId);
assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
assertThat(mEvent.getValue()).isEqualTo(event);
assertThat(mStste.getValue()).isEqualTo(state);
assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
}
public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
UserJourneyLogger.UserJourneySession session, int targetUserId, int event,
int state, int errorCode) {
mUserJourneyLogger.logUserLifecycleEventOccurred(session, targetUserId, event,
state, errorCode);
captureAndAssert(mUserJourneyLogger, session.mSessionId, targetUserId, event,
state, errorCode, 1);
}
}
}