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