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 }