fix: Refactor User Lifecycle Event and Report metrics logging

Improvements described in [this doc](https://docs.google.com/document/d/1X_f-kdVNnptKJnF9e5KO4WzbWqLNucJxPcnutk7cCgM/edit?resourcekey=0-LdjBpCMjyUxilDQlnyh6kg#)

Bug: 265927093
Test: atest UserJourneyLoggerTest
Change-Id: Id2632415b66834fe99fb627f9c9ed35d12ba207d
Merged-In: Id2632415b66834fe99fb627f9c9ed35d12ba207d
Merged-In: I102ed93f5a88a065f259b5cbaea42cc01c4a9ca1
This commit is contained in:
Anna Bauza
2023-01-23 14:06:47 +00:00
parent f806e9d7e7
commit b76fe23772
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 com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -2532,38 +2531,6 @@ public class UserManager {
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
* @deprecated Use {@link #isRestrictedProfile()}

View File

@@ -67,7 +67,6 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
@@ -78,6 +77,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal;
import dalvik.annotation.optimization.NeverCompile;
@@ -1518,7 +1518,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
final UserInfo userInfo =
(umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
if (userInfo != null) {
userType = UserManager.getUserTypeForStatsd(userInfo.userType);
userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
}
Slog.i(TAG_BROADCAST,
"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_UNLOCKED;
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_START_MODE_BACKGROUND_VISIBLE;
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.userStartModeToString;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -123,6 +134,8 @@ 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.UserJourneyLogger;
import com.android.server.pm.UserJourneyLogger.UserJourneySession;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserStartMode;
@@ -138,7 +151,6 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
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.
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.
*
@@ -419,13 +362,6 @@ class UserController implements Handler.Callback {
@GuardedBy("mLock")
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-
* 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
// storage is already unlocked.
if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
USER_LIFECYCLE_EVENT_STATE_NONE);
mInjector.getUserJourneyLogger()
.logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
EVENT_STATE_NONE);
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
@@ -694,8 +631,9 @@ class UserController implements Handler.Callback {
private boolean finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
mInjector.getUserJourneyLogger()
.logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
EVENT_STATE_BEGIN);
// If the user key hasn't been unlocked yet, we cannot proceed.
if (!StorageManager.isUserKeyUnlocked(userId)) return false;
synchronized (mLock) {
@@ -1073,9 +1011,7 @@ class UserController implements Handler.Callback {
return;
}
logUserJourneyInfo(null, getUserInfo(userId), USER_JOURNEY_USER_STOP);
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
logUserJourneyBegin(userId, USER_JOURNEY_USER_STOP);
if (stopUserCallback != null) {
uss.mStopCallbacks.add(stopUserCallback);
@@ -1138,9 +1074,16 @@ class UserController implements Handler.Callback {
synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) {
// Whoops, we are being started back up. Abort, abort!
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_NONE);
clearSessionId(userId);
UserJourneySession session = mInjector.getUserJourneyLogger()
.logUserJourneyFinishWithError(-1, getUserInfo(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;
}
uss.setState(UserState.STATE_SHUTDOWN);
@@ -1247,9 +1190,11 @@ class UserController implements Handler.Callback {
mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
}
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_FINISH);
clearSessionId(userId);
UserJourneySession session = mInjector.getUserJourneyLogger()
.logUserJourneyFinish(-1, userInfo, USER_JOURNEY_USER_STOP);
if (session != null) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
}
if (lockUser) {
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.
resumePendingUserStarts(userId);
} else {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_NONE);
clearSessionId(userId);
UserJourneySession session = mInjector.getUserJourneyLogger()
.finishAndClearIncompleteUserJourney(userId, USER_JOURNEY_USER_STOP);
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) {
switch (msg.what) {
case START_USER_SWITCH_FG_MSG:
logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1),
USER_JOURNEY_USER_SWITCH_FG);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_SWITCH_FG);
startUserInForeground(msg.arg1);
break;
case REPORT_USER_SWITCH_MSG:
@@ -3160,18 +3104,15 @@ class UserController implements Handler.Callback {
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
Integer.toString(msg.arg1), msg.arg1);
logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_START);
mInjector.onUserStarting(/* userId= */ 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);
clearSessionId(msg.arg1, USER_JOURNEY_USER_START);
mInjector.getUserJourneyLogger().logUserJourneyFinish(-1 , getUserInfo(msg.arg1),
USER_JOURNEY_USER_START);
break;
case USER_UNLOCK_MSG:
final int userId = msg.arg1;
@@ -3180,10 +3121,11 @@ class UserController implements Handler.Callback {
FgThread.getHandler().post(() -> {
mInjector.loadUserRecents(userId);
});
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
USER_LIFECYCLE_EVENT_STATE_FINISH);
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_FINISH);
mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_BEGIN);
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
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.)
// 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);
mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_FINISH);
// Unlocking user is not a journey no need to clear sessionId
break;
case USER_CURRENT_MSG:
mInjector.batteryStatsServiceNoteEvent(
@@ -3224,22 +3166,24 @@ class UserController implements Handler.Callback {
break;
case REPORT_USER_SWITCH_COMPLETE_MSG:
dispatchUserSwitchComplete(msg.arg1, msg.arg2);
logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_STATE_FINISH);
UserJourneySession session = mInjector.getUserJourneyLogger()
.logUserSwitchJourneyFinish(msg.arg1, getUserInfo(msg.arg2));
if (session != null) {
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
}
break;
case REPORT_LOCKED_BOOT_COMPLETE_MSG:
dispatchLockedBootComplete(msg.arg1);
break;
case START_USER_SWITCH_UI_MSG:
final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second,
USER_JOURNEY_USER_SWITCH_UI);
logUserLifecycleEvent(fromToUserPair.second.id, USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
logUserJourneyBegin(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI);
showUserSwitchDialog(fromToUserPair);
break;
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;
case COMPLETE_USER_SWITCH_MSG:
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
* atom given the originating and targeting users for the journey.
*/
private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) {
final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
synchronized (mUserIdToUserJourneyMap) {
UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(target.id);
if (userJourneySession != 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);
}
private void logUserJourneyBegin(int targetId,
@UserJourneyLogger.UserJourney int journey) {
UserJourneySession oldSession = mInjector.getUserJourneyLogger()
.finishAndClearIncompleteUserJourney(targetId, journey);
if (oldSession != null) {
if (DEBUG_MU) {
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
* the last expected lifecycle event for the journey. It may be possible that the last
* event is not called, e.g., user not unlocked after user switching. In such cases user
* journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
* User lifecycle journey would be complete when {@code #clearSessionId} is called
* after the last expected lifecycle event for the journey. It may be possible that
* the last event is not called, e.g., user not unlocked after user switching. In such
* cases user journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
*/
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS);
mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, oldSession);
}
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(
@@ -3471,23 +3321,6 @@ class UserController implements Handler.Callback {
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 volatile long mUnlockStarted;
@Override
@@ -3562,6 +3395,10 @@ class UserController implements Handler.Callback {
return new Handler(mService.mUiHandler.getLooper(), callback);
}
protected UserJourneyLogger getUserJourneyLogger() {
return getUserManager().getUserJourneyLogger();
}
protected Context getContext() {
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.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.accounts.Account;
import android.accounts.AccountManager;
@@ -162,7 +171,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -519,6 +527,8 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
private final UserJourneyLogger mUserJourneyLogger = new UserJourneyLogger();
private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -1580,45 +1590,56 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public void setUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("set user admin");
final long sessionId = logGrantAdminJourneyBegin(userId);
mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
UserInfo info;
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
info = getUserInfoLU(userId);
}
if (info == null || info.isAdmin()) {
// Exit if no user found with that id, or the user is already an Admin.
logUserJourneyError(sessionId,
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
userId);
if (info == null) {
// Exit if no user found with that id,
mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
getCurrentUserId(), userId, /* userType */ "", /* userFlags */ -1);
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;
}
info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(getUserDataLU(info.id));
}
logGrantAdminJourneyFinish(sessionId, userId, info.userType, info.flags);
mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_UNSPECIFIED);
}
@Override
public void revokeUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
final long sessionId = logRevokeAdminJourneyBegin(userId);
mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
UserData user;
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
user = getUserDataLU(userId);
if (user == null || !user.info.isAdmin()) {
// Exit if no user found with that id, or the user is not an Admin.
logUserJourneyError(sessionId, FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
userId);
if (user == null) {
// Exit if no user found with that id
mUserJourneyLogger.logNullUserJourneyError(
USER_JOURNEY_REVOKE_ADMIN,
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;
}
user.info.flags ^= UserInfo.FLAG_ADMIN;
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 TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("createUser-" + flags);
final long sessionId = logUserCreateJourneyBegin(noneUserId);
mUserJourneyLogger.logUserJourneyBegin(noneUserId, USER_JOURNEY_USER_CREATE);
UserInfo newUser = null;
try {
newUser = createUserInternalUncheckedNoTracing(name, userType, flags, parentId,
preCreate, disallowedPackages, t, token);
return newUser;
} finally {
logUserCreateJourneyFinish(sessionId,
newUser != null ? newUser.id : noneUserId, userType, flags,
newUser != null);
if (newUser != null) {
mUserJourneyLogger.logUserCreateJourneyFinish(getCurrentUserId(), newUser);
} else {
mUserJourneyLogger.logNullUserJourneyError(
USER_JOURNEY_USER_CREATE,
getCurrentUserId(), noneUserId, userType, flags);
}
t.traceEnd();
}
}
@@ -5198,137 +5223,6 @@ public class UserManagerService extends IUserManager.Stub {
&& !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. */
private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -5352,7 +5246,8 @@ public class UserManagerService extends IUserManager.Stub {
if (size > 1) {
for (int idx = 0; idx < size; 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
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
?
@@ -5635,7 +5530,7 @@ public class UserManagerService extends IUserManager.Stub {
writeUserLP(userData);
}
final long sessionId = logUserRemoveJourneyBegin(userId);
mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_USER_REMOVE);
try {
mAppOpsService.removeUser(userId);
@@ -5657,13 +5552,17 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public void userStopped(int userIdParam) {
finishRemoveUser(userIdParam);
logUserRemoveJourneyFinish(sessionId, userIdParam,
userData.info.userType, userData.info.flags, true);
int originUserId = UserManagerService.this.getCurrentUserId();
mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
userData.info, USER_JOURNEY_USER_REMOVE,
ERROR_CODE_UNSPECIFIED);
}
@Override
public void userStopAborted(int userIdParam) {
logUserRemoveJourneyFinish(sessionId, userIdParam,
userData.info.userType, userData.info.flags, false);
int originUserId = UserManagerService.this.getCurrentUserId();
mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
userData.info, USER_JOURNEY_USER_REMOVE,
ERROR_CODE_ABORTED);
}
});
} catch (RemoteException e) {
@@ -7297,9 +7196,9 @@ public class UserManagerService extends IUserManager.Stub {
final UserInfo userInfo = getUserInfo(userIds[i]);
if (userInfo == null) {
// Not possible because the input user ids should all be valid
userTypes[i] = UserManager.getUserTypeForStatsd("");
userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd("");
} else {
userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
}
}
return userTypes;
@@ -7536,4 +7435,11 @@ public class UserManagerService extends IUserManager.Stub {
.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.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -1074,6 +1075,8 @@ public class UserControllerTest {
private final KeyguardManager mKeyguardManagerMock;
private final LockPatternUtils mLockPatternUtilsMock;
private final UserJourneyLogger mUserJourneyLoggerMock;
private final Context mCtx;
TestInjector(Context ctx) {
@@ -1090,6 +1093,7 @@ public class UserControllerTest {
mKeyguardManagerMock = mock(KeyguardManager.class);
when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
mLockPatternUtilsMock = mock(LockPatternUtils.class);
mUserJourneyLoggerMock = mock(UserJourneyLogger.class);
}
@Override
@@ -1220,6 +1224,11 @@ public class UserControllerTest {
void onSystemUserVisibilityChanged(boolean visible) {
Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
}
@Override
protected UserJourneyLogger getUserJourneyLogger() {
return mUserJourneyLoggerMock;
}
}
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);
}
}
}