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 }