1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.pm;
18 
19 import static android.os.UserManager.USER_TYPE_FULL_DEMO;
20 import static android.os.UserManager.USER_TYPE_FULL_GUEST;
21 import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
22 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
23 import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
24 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
25 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
26 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
27 
28 import static com.android.internal.util.FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
29 
30 import android.annotation.IntDef;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.UserIdInt;
34 import android.content.pm.UserInfo;
35 import android.util.SparseArray;
36 
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.util.FrameworkStatsLog;
40 
41 import java.util.concurrent.ThreadLocalRandom;
42 
43 /**
44  * This class is logging User Lifecycle statsd events and synchronise User Lifecycle Journeys
45  * by making sure all events are called in correct order and errors are reported in case of
46  * unexpected journeys. This class also makes sure that all user sub-journeys are logged so
47  * for example User Switch also log User Start Journey.
48  */
49 public class UserJourneyLogger {
50 
51     public static final int ERROR_CODE_INVALID_SESSION_ID = 0;
52     public static final int ERROR_CODE_UNSPECIFIED = -1;
53     /*
54      * Possible reasons for ERROR_CODE_INCOMPLETE_OR_TIMEOUT to occur:
55      * - A user switch journey is received while another user switch journey is in
56      *   process for the same user.
57      * - A user switch journey is received while user start journey is in process for
58      *   the same user.
59      * - A user start journey is received while another user start journey is in process
60      *   for the same user.
61      * In all cases potentially an incomplete, timed-out session or multiple
62      * simultaneous requests. It is not possible to keep track of multiple sessions for
63      * the same user, so previous session is abandoned.
64      */
65     public static final int ERROR_CODE_INCOMPLETE_OR_TIMEOUT = 2;
66     public static final int ERROR_CODE_ABORTED = 3;
67     public static final int ERROR_CODE_NULL_USER_INFO = 4;
68     public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5;
69     public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6;
70 
71     @IntDef(prefix = {"ERROR_CODE"}, value = {
72             ERROR_CODE_UNSPECIFIED,
73             ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
74             ERROR_CODE_ABORTED,
75             ERROR_CODE_NULL_USER_INFO,
76             ERROR_CODE_USER_ALREADY_AN_ADMIN,
77             ERROR_CODE_USER_IS_NOT_AN_ADMIN,
78             ERROR_CODE_INVALID_SESSION_ID
79     })
80     public @interface UserJourneyErrorCode {
81     }
82 
83     // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
84     public static final int USER_JOURNEY_UNKNOWN =
85             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
86     public static final int USER_JOURNEY_USER_SWITCH_FG =
87             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
88     public static final int USER_JOURNEY_USER_SWITCH_UI =
89             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
90     public static final int USER_JOURNEY_USER_START =
91             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
92     public static final int USER_JOURNEY_USER_CREATE =
93             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
94     public static final int USER_JOURNEY_USER_STOP =
95             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
96     public static final int USER_JOURNEY_USER_REMOVE =
97             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE;
98     public static final int USER_JOURNEY_GRANT_ADMIN =
99             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN;
100     public static final int USER_JOURNEY_REVOKE_ADMIN =
101             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN;
102     public static final int USER_JOURNEY_USER_LIFECYCLE =
103             FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_LIFECYCLE;
104 
105     @IntDef(prefix = {"USER_JOURNEY"}, value = {
106             USER_JOURNEY_UNKNOWN,
107             USER_JOURNEY_USER_SWITCH_FG,
108             USER_JOURNEY_USER_SWITCH_UI,
109             USER_JOURNEY_USER_START,
110             USER_JOURNEY_USER_STOP,
111             USER_JOURNEY_USER_CREATE,
112             USER_JOURNEY_USER_REMOVE,
113             USER_JOURNEY_GRANT_ADMIN,
114             USER_JOURNEY_REVOKE_ADMIN,
115             USER_JOURNEY_USER_LIFECYCLE
116     })
117     public @interface UserJourney {
118     }
119 
120 
121     // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
122     public static final int USER_LIFECYCLE_EVENT_UNKNOWN =
123             USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
124     public static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
125             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
126     public static final int USER_LIFECYCLE_EVENT_START_USER =
127             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
128     public static final int USER_LIFECYCLE_EVENT_CREATE_USER =
129             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
130     public static final int USER_LIFECYCLE_EVENT_REMOVE_USER =
131             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
132     public static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
133             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
134     public static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
135             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
136     public static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
137             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
138     public static final int USER_LIFECYCLE_EVENT_STOP_USER =
139             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
140     public static final int USER_LIFECYCLE_EVENT_GRANT_ADMIN =
141             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
142     public static final int USER_LIFECYCLE_EVENT_REVOKE_ADMIN =
143             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
144 
145     @IntDef(prefix = {"USER_LIFECYCLE_EVENT"}, value = {
146             USER_LIFECYCLE_EVENT_UNKNOWN,
147             USER_LIFECYCLE_EVENT_SWITCH_USER,
148             USER_LIFECYCLE_EVENT_START_USER,
149             USER_LIFECYCLE_EVENT_CREATE_USER,
150             USER_LIFECYCLE_EVENT_REMOVE_USER,
151             USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
152             USER_LIFECYCLE_EVENT_UNLOCKING_USER,
153             USER_LIFECYCLE_EVENT_UNLOCKED_USER,
154             USER_LIFECYCLE_EVENT_STOP_USER,
155             USER_LIFECYCLE_EVENT_GRANT_ADMIN,
156             USER_LIFECYCLE_EVENT_REVOKE_ADMIN
157     })
158     public @interface UserLifecycleEvent {
159     }
160 
161     // User lifecycle event state, defined in the UserLifecycleEventOccurred atom for statsd
162     public static final int EVENT_STATE_BEGIN =
163             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
164     public static final int EVENT_STATE_FINISH =
165             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
166     public static final int EVENT_STATE_NONE =
167             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
168     public static final int EVENT_STATE_CANCEL =
169             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__CANCEL;
170     public static final int EVENT_STATE_ERROR =
171             FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR;
172 
173     @IntDef(prefix = {"EVENT_STATE"}, value = {
174             EVENT_STATE_BEGIN,
175             EVENT_STATE_FINISH,
176             EVENT_STATE_NONE,
177             EVENT_STATE_CANCEL,
178             EVENT_STATE_ERROR,
179     })
180     public @interface UserLifecycleEventState {
181     }
182 
183     private static final int USER_ID_KEY_MULTIPLICATION = 100;
184 
185     private final Object mLock = new Object();
186 
187     /**
188      * {@link UserIdInt} and {@link UserJourney} to {@link UserJourneySession} mapping used for
189      * statsd logging for the UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
190      */
191     @GuardedBy("mLock")
192     private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
193 
194     /**
195      * Returns event equivalent of given journey
196      */
197     @UserLifecycleEvent
journeyToEvent(@serJourney int journey)198     private static int journeyToEvent(@UserJourney int journey) {
199         switch (journey) {
200             case USER_JOURNEY_USER_SWITCH_UI:
201             case USER_JOURNEY_USER_SWITCH_FG:
202                 return USER_LIFECYCLE_EVENT_SWITCH_USER;
203             case USER_JOURNEY_USER_START:
204                 return USER_LIFECYCLE_EVENT_START_USER;
205             case USER_JOURNEY_USER_CREATE:
206                 return USER_LIFECYCLE_EVENT_CREATE_USER;
207             case USER_JOURNEY_USER_STOP:
208                 return USER_LIFECYCLE_EVENT_STOP_USER;
209             case USER_JOURNEY_USER_REMOVE:
210                 return USER_LIFECYCLE_EVENT_REMOVE_USER;
211             case USER_JOURNEY_GRANT_ADMIN:
212                 return USER_LIFECYCLE_EVENT_GRANT_ADMIN;
213             case USER_JOURNEY_REVOKE_ADMIN:
214                 return USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
215             default:
216                 return USER_LIFECYCLE_EVENT_UNKNOWN;
217         }
218     }
219 
220     /**
221      * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to
222      * the user type.
223      * Changes to this method require changes in CTS file
224      * com.android.cts.packagemanager.stats.device.UserInfoUtil
225      * which is duplicate for CTS tests purposes.
226      */
getUserTypeForStatsd(@onNull String userType)227     public static int getUserTypeForStatsd(@NonNull String userType) {
228         switch (userType) {
229             case USER_TYPE_FULL_SYSTEM:
230                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
231             case USER_TYPE_FULL_SECONDARY:
232                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
233             case USER_TYPE_FULL_GUEST:
234                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
235             case USER_TYPE_FULL_DEMO:
236                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
237             case USER_TYPE_FULL_RESTRICTED:
238                 return FrameworkStatsLog
239                         .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
240             case USER_TYPE_PROFILE_MANAGED:
241                 return FrameworkStatsLog
242                         .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
243             case USER_TYPE_SYSTEM_HEADLESS:
244                 return FrameworkStatsLog
245                         .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
246             case USER_TYPE_PROFILE_CLONE:
247                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
248             default:
249                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
250         }
251     }
252 
253     /**
254      * Map error code to the event finish state.
255      */
256     @UserLifecycleEventState
errorToFinishState(@serJourneyErrorCode int errorCode)257     private static int errorToFinishState(@UserJourneyErrorCode int errorCode) {
258         switch (errorCode) {
259             case ERROR_CODE_ABORTED:
260                 return EVENT_STATE_CANCEL;
261             case ERROR_CODE_UNSPECIFIED:
262                 return EVENT_STATE_FINISH;
263             default:
264                 return EVENT_STATE_ERROR;
265         }
266     }
267 
268     /**
269      * Simply logging USER_LIFECYCLE_JOURNEY_REPORTED if session exists.
270      * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
271      */
272     @VisibleForTesting
logUserLifecycleJourneyReported(@ullable UserJourneySession session, @UserJourney int journey, @UserIdInt int originalUserId, @UserIdInt int targetUserId, int userType, int userFlags, @UserJourneyErrorCode int errorCode)273     public void logUserLifecycleJourneyReported(@Nullable UserJourneySession session,
274             @UserJourney int journey, @UserIdInt int originalUserId, @UserIdInt int targetUserId,
275             int userType, int userFlags, @UserJourneyErrorCode int errorCode) {
276         if (session == null) {
277             writeUserLifecycleJourneyReported(-1, journey, originalUserId, targetUserId,
278                     userType, userFlags, ERROR_CODE_INVALID_SESSION_ID, -1);
279         } else {
280             final long elapsedTime = System.currentTimeMillis() - session.mStartTimeInMills;
281             writeUserLifecycleJourneyReported(
282                     session.mSessionId, journey, originalUserId, targetUserId, userType, userFlags,
283                     errorCode, elapsedTime);
284         }
285     }
286 
287     /**
288      * Helper method for spy testing
289      */
290     @VisibleForTesting
writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId, int targetUserId, int userType, int userFlags, int errorCode, long elapsedTime)291     public void writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId,
292             int targetUserId, int userType, int userFlags, int errorCode, long elapsedTime) {
293         FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED,
294                 sessionId, journey, originalUserId, targetUserId, userType, userFlags,
295                 errorCode, elapsedTime);
296     }
297 
298     /**
299      * Simply logging USER_LIFECYCLE_EVENT_OCCURRED if session exists.
300      * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
301      * and EVENT_STATE_ERROR
302      */
303     @VisibleForTesting
logUserLifecycleEventOccurred(UserJourneySession session, @UserIdInt int targetUserId, @UserLifecycleEvent int event, @UserLifecycleEventState int state, @UserJourneyErrorCode int errorCode)304     public void logUserLifecycleEventOccurred(UserJourneySession session,
305             @UserIdInt int targetUserId, @UserLifecycleEvent int event,
306             @UserLifecycleEventState int state, @UserJourneyErrorCode int errorCode) {
307         if (session == null) {
308             writeUserLifecycleEventOccurred(-1, targetUserId, event,
309                     EVENT_STATE_ERROR, ERROR_CODE_INVALID_SESSION_ID);
310         } else {
311             writeUserLifecycleEventOccurred(session.mSessionId, targetUserId, event, state,
312                     errorCode);
313         }
314     }
315 
316     /**
317      * Helper method for spy testing
318      */
319     @VisibleForTesting
writeUserLifecycleEventOccurred(long sessionId, int userId, int event, int state, int errorCode)320     public void writeUserLifecycleEventOccurred(long sessionId, int userId, int event, int state,
321             int errorCode) {
322         FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
323                 sessionId, userId, event, state, errorCode);
324     }
325 
326     /**
327      * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
328      * atom. It finds the user journey session for target user id and logs it as that journey.
329      */
logUserLifecycleEvent(@serIdInt int userId, @UserLifecycleEvent int event, @UserLifecycleEventState int eventState)330     public void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
331             @UserLifecycleEventState int eventState) {
332         final UserJourneySession userJourneySession = findUserJourneySession(userId);
333         logUserLifecycleEventOccurred(userJourneySession, userId,
334                 event, eventState, UserJourneyLogger.ERROR_CODE_UNSPECIFIED);
335     }
336 
337     /**
338      * Returns first user session from mUserIdToUserJourneyMap for given user id,
339      * or null if user id was not found in mUserIdToUserJourneyMap.
340      */
findUserJourneySession(@serIdInt int userId)341     private @Nullable UserJourneySession findUserJourneySession(@UserIdInt int userId) {
342         synchronized (mLock) {
343             final int keyMapSize = mUserIdToUserJourneyMap.size();
344             for (int i = 0; i < keyMapSize; i++) {
345                 int key = mUserIdToUserJourneyMap.keyAt(i);
346                 if (key / USER_ID_KEY_MULTIPLICATION == userId) {
347                     return mUserIdToUserJourneyMap.get(key);
348                 }
349             }
350         }
351         return null;
352     }
353 
354     /**
355      * Returns unique id for user and journey. For example if user id = 11 and journey = 7
356      * then unique key = 11 * 100 + 7 = 1107
357      */
getUserJourneyKey(@serIdInt int targetUserId, @UserJourney int journey)358     private int getUserJourneyKey(@UserIdInt int targetUserId, @UserJourney int journey) {
359         // We leave 99 for user journeys ids.
360         return (targetUserId * USER_ID_KEY_MULTIPLICATION) + journey;
361     }
362 
363     /**
364      * Special use case when user journey incomplete or timeout and current user is unclear
365      */
366     @VisibleForTesting
finishAndClearIncompleteUserJourney(@serIdInt int targetUserId, @UserJourney int journey)367     public UserJourneySession finishAndClearIncompleteUserJourney(@UserIdInt int targetUserId,
368             @UserJourney int journey) {
369         synchronized (mLock) {
370             final int key = getUserJourneyKey(targetUserId, journey);
371             final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
372             if (userJourneySession != null) {
373                 logUserLifecycleEventOccurred(
374                         userJourneySession,
375                         targetUserId,
376                         journeyToEvent(userJourneySession.mJourney),
377                         EVENT_STATE_ERROR,
378                         UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
379 
380                 logUserLifecycleJourneyReported(
381                         userJourneySession,
382                         journey,
383                         /* originalUserId= */ -1,
384                         targetUserId,
385                         getUserTypeForStatsd(""), -1,
386                         ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
387                 mUserIdToUserJourneyMap.remove(key);
388 
389                 return userJourneySession;
390             }
391         }
392         return null;
393     }
394 
395     /**
396      * Log user journey event and report finishing without error
397      */
logUserJourneyFinish(@serIdInt int originalUserId, UserInfo targetUser, @UserJourney int journey)398     public UserJourneySession logUserJourneyFinish(@UserIdInt int originalUserId,
399             UserInfo targetUser, @UserJourney int journey) {
400         return logUserJourneyFinishWithError(originalUserId, targetUser, journey,
401                 ERROR_CODE_UNSPECIFIED);
402     }
403 
404     /**
405      * Special case when it is unknown which user switch  journey was used and checking both
406      */
407     @VisibleForTesting
logUserSwitchJourneyFinish(@serIdInt int originalUserId, UserInfo targetUser)408     public UserJourneySession logUserSwitchJourneyFinish(@UserIdInt int originalUserId,
409             UserInfo targetUser) {
410         synchronized (mLock) {
411             final int key_fg = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_FG);
412             final int key_ui = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_UI);
413 
414             if (mUserIdToUserJourneyMap.contains(key_fg)) {
415                 return logUserJourneyFinish(originalUserId, targetUser,
416                         USER_JOURNEY_USER_SWITCH_FG);
417             }
418 
419             if (mUserIdToUserJourneyMap.contains(key_ui)) {
420                 return logUserJourneyFinish(originalUserId, targetUser,
421                         USER_JOURNEY_USER_SWITCH_UI);
422             }
423 
424             return null;
425         }
426     }
427 
428     /**
429      * Log user journey event and report finishing with error
430      */
logUserJourneyFinishWithError(@serIdInt int originalUserId, UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode)431     public UserJourneySession logUserJourneyFinishWithError(@UserIdInt int originalUserId,
432             UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
433         synchronized (mLock) {
434             final int state = errorToFinishState(errorCode);
435             final int key = getUserJourneyKey(targetUser.id, journey);
436             final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
437             if (userJourneySession != null) {
438                 logUserLifecycleEventOccurred(
439                         userJourneySession, targetUser.id,
440                         journeyToEvent(userJourneySession.mJourney),
441                         state,
442                         errorCode);
443 
444                 logUserLifecycleJourneyReported(
445                         userJourneySession,
446                         journey, originalUserId, targetUser.id,
447                         getUserTypeForStatsd(targetUser.userType),
448                         targetUser.flags,
449                         errorCode);
450                 mUserIdToUserJourneyMap.remove(key);
451 
452                 return userJourneySession;
453             }
454         }
455         return null;
456     }
457 
458     /**
459      * Log user journey event and report finishing with error
460      */
logDelayedUserJourneyFinishWithError(@serIdInt int originalUserId, UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode)461     public UserJourneySession logDelayedUserJourneyFinishWithError(@UserIdInt int originalUserId,
462             UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
463         synchronized (mLock) {
464             final int key = getUserJourneyKey(targetUser.id, journey);
465             final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
466             if (userJourneySession != null) {
467                 logUserLifecycleJourneyReported(
468                         userJourneySession,
469                         journey, originalUserId, targetUser.id,
470                         getUserTypeForStatsd(targetUser.userType),
471                         targetUser.flags,
472                         errorCode);
473                 mUserIdToUserJourneyMap.remove(key);
474 
475                 return userJourneySession;
476             }
477         }
478         return null;
479     }
480 
481     /**
482      * Log event and report finish when user is null. This is edge case when UserInfo
483      * can not be passed because it is null, therefore all information are passed as arguments.
484      */
logNullUserJourneyError(@serJourney int journey, @UserIdInt int currentUserId, @UserIdInt int targetUserId, String targetUserType, int targetUserFlags)485     public UserJourneySession logNullUserJourneyError(@UserJourney int journey,
486             @UserIdInt int currentUserId, @UserIdInt int targetUserId, String targetUserType,
487             int targetUserFlags) {
488         synchronized (mLock) {
489             final int key = getUserJourneyKey(targetUserId, journey);
490             final UserJourneySession session = mUserIdToUserJourneyMap.get(key);
491 
492             logUserLifecycleEventOccurred(
493                     session, targetUserId, journeyToEvent(journey),
494                     EVENT_STATE_ERROR,
495                     ERROR_CODE_NULL_USER_INFO);
496 
497             logUserLifecycleJourneyReported(
498                     session, journey, currentUserId, targetUserId,
499                     getUserTypeForStatsd(targetUserType), targetUserFlags,
500                     ERROR_CODE_NULL_USER_INFO);
501 
502             mUserIdToUserJourneyMap.remove(key);
503             return session;
504         }
505     }
506 
507     /**
508      * Log for user creation finish event and report. This is edge case when target user id is
509      * different in begin event and finish event as it is unknown what is user id
510      * until it has been created.
511      */
logUserCreateJourneyFinish(@serIdInt int originalUserId, UserInfo targetUser)512     public UserJourneySession logUserCreateJourneyFinish(@UserIdInt int originalUserId,
513             UserInfo targetUser) {
514         synchronized (mLock) {
515             // we do not know user id until we create new user which is why we use -1
516             // as user id to create and find session, but we log correct id.
517             final int key = getUserJourneyKey(-1, USER_JOURNEY_USER_CREATE);
518             final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
519             if (userJourneySession != null) {
520                 logUserLifecycleEventOccurred(
521                         userJourneySession, targetUser.id,
522                         USER_LIFECYCLE_EVENT_CREATE_USER,
523                         EVENT_STATE_FINISH,
524                         ERROR_CODE_UNSPECIFIED);
525 
526                 logUserLifecycleJourneyReported(
527                         userJourneySession,
528                         USER_JOURNEY_USER_CREATE, originalUserId, targetUser.id,
529                         getUserTypeForStatsd(targetUser.userType),
530                         targetUser.flags,
531                         ERROR_CODE_UNSPECIFIED);
532                 mUserIdToUserJourneyMap.remove(key);
533 
534                 return userJourneySession;
535             }
536         }
537         return null;
538     }
539 
540     /**
541      * Adds new UserJourneySession to mUserIdToUserJourneyMap and log UserJourneyEvent Begin state
542      */
logUserJourneyBegin(@serIdInt int targetId, @UserJourney int journey)543     public UserJourneySession logUserJourneyBegin(@UserIdInt int targetId,
544             @UserJourney int journey) {
545         final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
546         synchronized (mLock) {
547             final int key = getUserJourneyKey(targetId, journey);
548             final UserJourneySession userJourneySession =
549                     new UserJourneySession(newSessionId, journey);
550             mUserIdToUserJourneyMap.append(key, userJourneySession);
551 
552             logUserLifecycleEventOccurred(
553                     userJourneySession, targetId,
554                     journeyToEvent(userJourneySession.mJourney),
555                     EVENT_STATE_BEGIN,
556                     ERROR_CODE_UNSPECIFIED);
557 
558             return userJourneySession;
559         }
560     }
561 
562     /**
563      * This keeps the start time when finishing extensively long journey was began.
564      * For instance full user lifecycle ( from creation to deletion )when user is about to delete
565      * we need to get user creation time before it was deleted.
566      */
startSessionForDelayedJourney(@serIdInt int targetId, @UserJourney int journey, long startTime)567     public UserJourneySession startSessionForDelayedJourney(@UserIdInt int targetId,
568             @UserJourney int journey, long startTime) {
569         final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
570         synchronized (mLock) {
571             final int key = getUserJourneyKey(targetId, journey);
572             final UserJourneySession userJourneySession =
573                     new UserJourneySession(newSessionId, journey, startTime);
574             mUserIdToUserJourneyMap.append(key, userJourneySession);
575             return userJourneySession;
576         }
577     }
578 
579     /**
580      * Helper class to store user journey and session id.
581      *
582      * <p> User journey tracks a chain of user lifecycle events occurring during different user
583      * activities such as user start, user switch, and user creation.
584      */
585     public static class UserJourneySession {
586         public final long mSessionId;
587         @UserJourney
588         public final int mJourney;
589         public long mStartTimeInMills;
590 
591         @VisibleForTesting
UserJourneySession(long sessionId, @UserJourney int journey)592         public UserJourneySession(long sessionId, @UserJourney int journey) {
593             mJourney = journey;
594             mSessionId = sessionId;
595             mStartTimeInMills = System.currentTimeMillis();
596         }
597         @VisibleForTesting
UserJourneySession(long sessionId, @UserJourney int journey, long startTimeInMills)598         public UserJourneySession(long sessionId, @UserJourney int journey, long startTimeInMills) {
599             mJourney = journey;
600             mSessionId = sessionId;
601             mStartTimeInMills = startTimeInMills;
602         }
603     }
604 }