1 /* 2 * Copyright (C) 2022 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.wm; 18 19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 20 21 import static com.android.server.wm.SnapshotPersistQueue.MAX_STORE_QUEUE_DEPTH; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.graphics.Rect; 27 import android.os.Environment; 28 import android.os.Trace; 29 import android.util.ArraySet; 30 import android.util.IntArray; 31 import android.util.Slog; 32 import android.util.SparseArray; 33 import android.window.TaskSnapshot; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; 37 38 import java.io.File; 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.function.Supplier; 42 43 /** 44 * When an app token becomes invisible, we take a snapshot (bitmap) and put it into our cache. 45 * Internally we use gralloc buffers to be able to draw them wherever we like without any copying. 46 * <p> 47 * System applications may retrieve a snapshot to represent the current state of an activity, and 48 * draw them in their own process. 49 * <p> 50 * Unlike TaskSnapshotController, we only keep one activity snapshot for a visible task in the 51 * cache. Which should largely reduce the memory usage. 52 * <p> 53 * To access this class, acquire the global window manager lock. 54 */ 55 class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord, 56 ActivitySnapshotCache> { 57 private static final boolean DEBUG = false; 58 private static final String TAG = AbsAppSnapshotController.TAG; 59 // Maximum persisted snapshot count on disk. 60 private static final int MAX_PERSIST_SNAPSHOT_COUNT = 20; 61 62 static final String SNAPSHOTS_DIRNAME = "activity_snapshots"; 63 64 /** 65 * The pending activities which should remove snapshot from memory when process transition 66 * finish. 67 */ 68 @VisibleForTesting 69 final ArraySet<ActivityRecord> mPendingRemoveActivity = new ArraySet<>(); 70 71 /** 72 * The pending activities which should delete snapshot files when process transition finish. 73 */ 74 @VisibleForTesting 75 final ArraySet<ActivityRecord> mPendingDeleteActivity = new ArraySet<>(); 76 77 /** 78 * The pending activities which should load snapshot from disk when process transition finish. 79 */ 80 @VisibleForTesting 81 final ArraySet<ActivityRecord> mPendingLoadActivity = new ArraySet<>(); 82 83 private final ArraySet<ActivityRecord> mOnBackPressedActivities = new ArraySet<>(); 84 85 private final ArrayList<ActivityRecord> mTmpBelowActivities = new ArrayList<>(); 86 private final ArrayList<WindowContainer> mTmpTransitionParticipants = new ArrayList<>(); 87 private final SnapshotPersistQueue mSnapshotPersistQueue; 88 private final PersistInfoProvider mPersistInfoProvider; 89 private final AppSnapshotLoader mSnapshotLoader; 90 91 /** 92 * File information holders, to make the sequence align, always update status of 93 * mUserSavedFiles/mSavedFilesInOrder before persist file from mPersister. 94 */ 95 private final SparseArray<SparseArray<UserSavedFile>> mUserSavedFiles = new SparseArray<>(); 96 // Keep sorted with create timeline. 97 private final ArrayList<UserSavedFile> mSavedFilesInOrder = new ArrayList<>(); 98 private final TaskSnapshotPersister mPersister; 99 ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue)100 ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) { 101 super(service); 102 final boolean snapshotEnabled = 103 !service.mContext 104 .getResources() 105 .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots) 106 && !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go 107 setSnapshotEnabled(snapshotEnabled); 108 mSnapshotPersistQueue = persistQueue; 109 mPersistInfoProvider = createPersistInfoProvider(service); 110 mPersister = new TaskSnapshotPersister( 111 persistQueue, 112 mPersistInfoProvider, 113 shouldDisableSnapshots()); 114 mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider); 115 initialize(new ActivitySnapshotCache()); 116 } 117 118 @VisibleForTesting createPersistInfoProvider(WindowManagerService service)119 PersistInfoProvider createPersistInfoProvider(WindowManagerService service) { 120 return createPersistInfoProvider(service, Environment::getDataSystemCeDirectory); 121 } 122 123 @Override initSnapshotScale()124 protected float initSnapshotScale() { 125 final float config = mService.mContext.getResources().getFloat( 126 com.android.internal.R.dimen.config_resActivitySnapshotScale); 127 return Math.max(Math.min(config, 1f), 0.1f); 128 } 129 createPersistInfoProvider( WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver)130 static PersistInfoProvider createPersistInfoProvider( 131 WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver) { 132 // Don't persist reduced file, instead we only persist the "HighRes" bitmap which has 133 // already scaled with #initSnapshotScale 134 final boolean use16BitFormat = service.mContext.getResources().getBoolean( 135 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat); 136 return new PersistInfoProvider(resolver, SNAPSHOTS_DIRNAME, 137 false /* enableLowResSnapshots */, 0 /* lowResScaleFactor */, use16BitFormat); 138 } 139 140 /** 141 * Retrieves a snapshot for a set of activities from cache. 142 * This will only return the snapshot IFF input activities exist entirely in the snapshot. 143 * Sample: If the snapshot was captured with activity A and B, here will return null if the 144 * input activity is only [A] or [B], it must be [A, B] 145 */ 146 @Nullable getSnapshot(@onNull ActivityRecord[] activities)147 TaskSnapshot getSnapshot(@NonNull ActivityRecord[] activities) { 148 if (activities.length == 0) { 149 return null; 150 } 151 final UserSavedFile tmpUsf = findSavedFile(activities[0]); 152 if (tmpUsf == null || tmpUsf.mActivityIds.size() != activities.length) { 153 return null; 154 } 155 int fileId = 0; 156 for (int i = activities.length - 1; i >= 0; --i) { 157 fileId ^= getSystemHashCode(activities[i]); 158 } 159 return tmpUsf.mFileId == fileId 160 ? mCache.getSnapshotInner(tmpUsf.mActivityIds.get(0)) : null; 161 } 162 cleanUpUserFiles(int userId)163 private void cleanUpUserFiles(int userId) { 164 synchronized (mSnapshotPersistQueue.getLock()) { 165 mSnapshotPersistQueue.sendToQueueLocked( 166 new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider, userId) { 167 168 @Override 169 void write() { 170 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cleanUpUserFiles"); 171 final File file = mPersistInfoProvider.getDirectory(mUserId); 172 if (file.exists()) { 173 final File[] contents = file.listFiles(); 174 if (contents != null) { 175 for (int i = contents.length - 1; i >= 0; i--) { 176 contents[i].delete(); 177 } 178 } 179 } 180 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 181 } 182 }); 183 } 184 } 185 addOnBackPressedActivity(ActivityRecord ar)186 void addOnBackPressedActivity(ActivityRecord ar) { 187 if (shouldDisableSnapshots()) { 188 return; 189 } 190 mOnBackPressedActivities.add(ar); 191 } 192 clearOnBackPressedActivities()193 void clearOnBackPressedActivities() { 194 if (shouldDisableSnapshots()) { 195 return; 196 } 197 mOnBackPressedActivities.clear(); 198 } 199 200 /** 201 * Prepare to collect any change for snapshots processing. Clear all temporary fields. 202 */ beginSnapshotProcess()203 void beginSnapshotProcess() { 204 if (shouldDisableSnapshots()) { 205 return; 206 } 207 resetTmpFields(); 208 } 209 210 /** 211 * End collect any change for snapshots processing, start process data. 212 */ endSnapshotProcess()213 void endSnapshotProcess() { 214 if (shouldDisableSnapshots()) { 215 return; 216 } 217 for (int i = mOnBackPressedActivities.size() - 1; i >= 0; --i) { 218 handleActivityTransition(mOnBackPressedActivities.valueAt(i)); 219 } 220 mOnBackPressedActivities.clear(); 221 mTmpTransitionParticipants.clear(); 222 postProcess(); 223 } 224 225 @VisibleForTesting resetTmpFields()226 void resetTmpFields() { 227 mPendingRemoveActivity.clear(); 228 mPendingDeleteActivity.clear(); 229 mPendingLoadActivity.clear(); 230 } 231 232 /** 233 * Start process all pending activities for a transition. 234 */ postProcess()235 private void postProcess() { 236 if (DEBUG) { 237 Slog.d(TAG, "ActivitySnapshotController#postProcess result:" 238 + " remove " + mPendingRemoveActivity 239 + " delete " + mPendingDeleteActivity 240 + " load " + mPendingLoadActivity); 241 } 242 // load snapshot to cache 243 loadActivitySnapshot(); 244 // clear mTmpRemoveActivity from cache 245 for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) { 246 final ActivityRecord ar = mPendingRemoveActivity.valueAt(i); 247 removeCachedFiles(ar); 248 } 249 // clear snapshot on cache and delete files 250 for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) { 251 final ActivityRecord ar = mPendingDeleteActivity.valueAt(i); 252 removeIfUserSavedFileExist(ar); 253 } 254 // don't keep any reference 255 resetTmpFields(); 256 } 257 258 class LoadActivitySnapshotItem extends SnapshotPersistQueue.WriteQueueItem { 259 private final int mCode; 260 private final ActivityRecord[] mActivities; 261 LoadActivitySnapshotItem(@onNull ActivityRecord[] activities, int code, int userId, @NonNull PersistInfoProvider persistInfoProvider)262 LoadActivitySnapshotItem(@NonNull ActivityRecord[] activities, int code, int userId, 263 @NonNull PersistInfoProvider persistInfoProvider) { 264 super(persistInfoProvider, userId); 265 mActivities = activities; 266 mCode = code; 267 } 268 269 @Override write()270 void write() { 271 try { 272 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, 273 "load_activity_snapshot"); 274 final TaskSnapshot snapshot = mSnapshotLoader.loadTask(mCode, 275 mUserId, false /* loadLowResolutionBitmap */); 276 if (snapshot == null) { 277 return; 278 } 279 synchronized (mService.getWindowManagerLock()) { 280 // Verify the snapshot is still needed, and the activity is not finishing 281 if (!hasRecord(mActivities[0])) { 282 return; 283 } 284 for (ActivityRecord ar : mActivities) { 285 mCache.putSnapshot(ar, snapshot); 286 } 287 } 288 } finally { 289 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 290 } 291 } 292 293 @Override equals(Object o)294 public boolean equals(Object o) { 295 if (o == null || getClass() != o.getClass()) return false; 296 final LoadActivitySnapshotItem other = (LoadActivitySnapshotItem) o; 297 return mCode == other.mCode && mUserId == other.mUserId 298 && mPersistInfoProvider == other.mPersistInfoProvider; 299 } 300 301 @Override toString()302 public String toString() { 303 return "LoadActivitySnapshotItem{code=" + mCode + ", UserId=" + mUserId + "}"; 304 } 305 } 306 loadActivitySnapshot()307 void loadActivitySnapshot() { 308 if (mPendingLoadActivity.isEmpty()) { 309 return; 310 } 311 // Only load if saved file exists. 312 final ArraySet<UserSavedFile> loadingFiles = new ArraySet<>(); 313 for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) { 314 final ActivityRecord ar = mPendingLoadActivity.valueAt(i); 315 final UserSavedFile usf = findSavedFile(ar); 316 if (usf != null) { 317 loadingFiles.add(usf); 318 } 319 } 320 // Filter out the activity if the snapshot was removed. 321 for (int i = loadingFiles.size() - 1; i >= 0; i--) { 322 final UserSavedFile usf = loadingFiles.valueAt(i); 323 final ActivityRecord[] activities = usf.filterExistActivities(mPendingLoadActivity); 324 if (activities == null) { 325 continue; 326 } 327 if (getSnapshot(activities) != null) { 328 // Found the cache in memory, so skip loading from file. 329 continue; 330 } 331 loadSnapshotInner(activities, usf); 332 } 333 } 334 335 @VisibleForTesting loadSnapshotInner(ActivityRecord[] activities, UserSavedFile usf)336 void loadSnapshotInner(ActivityRecord[] activities, UserSavedFile usf) { 337 synchronized (mSnapshotPersistQueue.getLock()) { 338 mSnapshotPersistQueue.insertQueueAtFirstLocked(new LoadActivitySnapshotItem( 339 activities, usf.mFileId, usf.mUserId, mPersistInfoProvider)); 340 } 341 } 342 343 /** 344 * Record one or multiple activities within a snapshot where those activities must belong to 345 * the same task. 346 * @param activity If the request activity is more than one, try to record those activities 347 * as a single snapshot, so those activities should belong to the same task. 348 */ recordSnapshot(@onNull ArrayList<ActivityRecord> activity)349 void recordSnapshot(@NonNull ArrayList<ActivityRecord> activity) { 350 if (shouldDisableSnapshots() || activity.isEmpty()) { 351 return; 352 } 353 if (DEBUG) { 354 Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity); 355 } 356 if (mPersister.mSnapshotPersistQueue.peekWriteQueueSize() >= MAX_STORE_QUEUE_DEPTH 357 || mPersister.mSnapshotPersistQueue.peekQueueSize() > MAX_PERSIST_SNAPSHOT_COUNT) { 358 Slog.w(TAG, "Skipping recording activity snapshot, too many requests!"); 359 return; 360 } 361 final int size = activity.size(); 362 final int[] mixedCode = new int[size]; 363 if (size == 1) { 364 final ActivityRecord singleActivity = activity.get(0); 365 final Supplier<TaskSnapshot> supplier = recordSnapshotInner(singleActivity, 366 false /* allowAppTheme */, null /* inLockConsumer */); 367 final TaskSnapshot snapshot = supplier != null ? supplier.get() : null; 368 if (snapshot != null) { 369 mixedCode[0] = getSystemHashCode(singleActivity); 370 addUserSavedFile(singleActivity.mUserId, snapshot, mixedCode); 371 } 372 return; 373 } 374 375 final Task mainTask = activity.get(0).getTask(); 376 // Snapshot by task controller with activity's scale. 377 final TaskSnapshot snapshot = mService.mTaskSnapshotController 378 .snapshot(mainTask, mHighResSnapshotScale); 379 if (snapshot == null) { 380 return; 381 } 382 383 for (int i = 0; i < activity.size(); ++i) { 384 final ActivityRecord next = activity.get(i); 385 mCache.putSnapshot(next, snapshot); 386 mixedCode[i] = getSystemHashCode(next); 387 } 388 addUserSavedFile(mainTask.mUserId, snapshot, mixedCode); 389 } 390 391 /** 392 * Called when the visibility of an app changes outside the regular app transition flow. 393 */ notifyAppVisibilityChanged(ActivityRecord ar, boolean visible)394 void notifyAppVisibilityChanged(ActivityRecord ar, boolean visible) { 395 if (shouldDisableSnapshots()) { 396 return; 397 } 398 final Task task = ar.getTask(); 399 if (task == null) { 400 return; 401 } 402 // Doesn't need to capture activity snapshot when it converts from translucent. 403 if (!visible) { 404 resetTmpFields(); 405 addBelowActivityIfExist(ar, mPendingRemoveActivity, false, 406 "remove-snapshot"); 407 postProcess(); 408 } 409 } 410 411 @VisibleForTesting getSystemHashCode(ActivityRecord activity)412 static int getSystemHashCode(ActivityRecord activity) { 413 return System.identityHashCode(activity); 414 } 415 416 @VisibleForTesting handleTransitionFinish(@onNull ArrayList<WindowContainer> windows)417 void handleTransitionFinish(@NonNull ArrayList<WindowContainer> windows) { 418 mTmpTransitionParticipants.clear(); 419 mTmpTransitionParticipants.addAll(windows); 420 for (int i = mTmpTransitionParticipants.size() - 1; i >= 0; --i) { 421 final WindowContainer next = mTmpTransitionParticipants.get(i); 422 if (next.asTask() != null) { 423 handleTaskTransition(next.asTask()); 424 } else if (next.asTaskFragment() != null) { 425 final TaskFragment tf = next.asTaskFragment(); 426 final ActivityRecord ar = tf.getTopMostActivity(); 427 if (ar != null) { 428 handleActivityTransition(ar); 429 } 430 } else if (next.asActivityRecord() != null) { 431 handleActivityTransition(next.asActivityRecord()); 432 } 433 } 434 } 435 handleActivityTransition(@onNull ActivityRecord ar)436 private void handleActivityTransition(@NonNull ActivityRecord ar) { 437 if (shouldDisableSnapshots()) { 438 return; 439 } 440 if (ar.isVisibleRequested()) { 441 mPendingDeleteActivity.add(ar); 442 // load next one if exists. 443 // Note if this transition is happen between two TaskFragment, the next N - 1 activity 444 // may not participant in this transition. 445 // Sample: 446 // [TF1] close 447 // [TF2] open 448 // Bottom Activity <- Able to load this even it didn't participant the transition. 449 addBelowActivityIfExist(ar, mPendingLoadActivity, false, "load-snapshot"); 450 } else { 451 // remove the snapshot for the one below close 452 addBelowActivityIfExist(ar, mPendingRemoveActivity, false, "remove-snapshot"); 453 } 454 } 455 handleTaskTransition(Task task)456 private void handleTaskTransition(Task task) { 457 if (shouldDisableSnapshots()) { 458 return; 459 } 460 final ActivityRecord topActivity = task.getTopMostActivity(); 461 if (topActivity == null) { 462 return; 463 } 464 if (task.isVisibleRequested()) { 465 // this is open task transition 466 // load the N - 1 to cache 467 addBelowActivityIfExist(topActivity, mPendingLoadActivity, true, "load-snapshot"); 468 // Move the activities to top of mSavedFilesInOrder, so when purge happen, there 469 // will trim the persisted files from the most non-accessed. 470 adjustSavedFileOrder(task); 471 } else { 472 // this is close task transition 473 // remove the N - 1 from cache 474 addBelowActivityIfExist(topActivity, mPendingRemoveActivity, true, "remove-snapshot"); 475 } 476 } 477 478 /** 479 * Add the top -1 activity to a set if it exists. 480 * @param inTransition true if the activity must participant in transition. 481 */ addBelowActivityIfExist(ActivityRecord currentActivity, ArraySet<ActivityRecord> set, boolean inTransition, String debugMessage)482 private void addBelowActivityIfExist(ActivityRecord currentActivity, 483 ArraySet<ActivityRecord> set, boolean inTransition, String debugMessage) { 484 getActivityBelow(currentActivity, inTransition, mTmpBelowActivities); 485 for (int i = mTmpBelowActivities.size() - 1; i >= 0; --i) { 486 set.add(mTmpBelowActivities.get(i)); 487 if (DEBUG) { 488 Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist " 489 + mTmpBelowActivities.get(i) + " from " + debugMessage); 490 } 491 } 492 mTmpBelowActivities.clear(); 493 } 494 getActivityBelow(ActivityRecord currentActivity, boolean inTransition, ArrayList<ActivityRecord> result)495 private void getActivityBelow(ActivityRecord currentActivity, boolean inTransition, 496 ArrayList<ActivityRecord> result) { 497 final Task currentTask = currentActivity.getTask(); 498 if (currentTask == null) { 499 return; 500 } 501 final ActivityRecord initPrev = currentTask.getActivityBelow(currentActivity); 502 if (initPrev == null) { 503 return; 504 } 505 final TaskFragment currTF = currentActivity.getTaskFragment(); 506 final TaskFragment prevTF = initPrev.getTaskFragment(); 507 if (currTF == prevTF || prevTF.asTask() != null || !prevTF.hasAdjacentTaskFragment()) { 508 // Current activity and the initPrev is in the same TaskFragment, 509 // or initPrev activity is a direct child of Task, 510 // or initPrev activity doesn't have an adjacent. 511 // A 512 // B 513 if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) { 514 result.add(initPrev); 515 } 516 return; 517 } 518 519 if (currTF.isAdjacentTo(prevTF)) { 520 // previous activity A is adjacent to current activity B. 521 // Try to find anyone below previous activityA, which are C and D if exists. 522 // A | B 523 // C (| D) 524 getActivityBelow(initPrev, inTransition, result); 525 return; 526 } 527 528 // The initPrev activity has an adjacent that is different from current activity. 529 // A 530 // B | C 531 final int currentIndex = currTF.asTask() != null 532 ? currentTask.mChildren.indexOf(currentActivity) 533 : currentTask.mChildren.indexOf(currTF); 534 final boolean hasAdjacentAboveCurrent = prevTF.forOtherAdjacentTaskFragments( 535 prevAdjacentTF -> { 536 final int prevAdjacentIndex = currentTask.mChildren.indexOf(prevAdjacentTF); 537 return prevAdjacentIndex > currentIndex; 538 }); 539 if (hasAdjacentAboveCurrent) { 540 // PrevAdjacentTF already above currentActivity 541 return; 542 } 543 // Add all adjacent top. 544 if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) { 545 result.add(initPrev); 546 } 547 prevTF.forOtherAdjacentTaskFragments(prevAdjacentTF -> { 548 final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity(); 549 if (prevAdjacentActivity != null && (!inTransition 550 || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) { 551 result.add(prevAdjacentActivity); 552 } 553 }); 554 } 555 isInParticipant(ActivityRecord ar, ArrayList<WindowContainer> transitionParticipants)556 static boolean isInParticipant(ActivityRecord ar, 557 ArrayList<WindowContainer> transitionParticipants) { 558 for (int i = transitionParticipants.size() - 1; i >= 0; --i) { 559 final WindowContainer wc = transitionParticipants.get(i); 560 if (ar == wc || ar.isDescendantOf(wc)) { 561 return true; 562 } 563 } 564 return false; 565 } 566 adjustSavedFileOrder(Task nextTopTask)567 private void adjustSavedFileOrder(Task nextTopTask) { 568 nextTopTask.forAllActivities(ar -> { 569 final UserSavedFile usf = findSavedFile(ar); 570 if (usf != null) { 571 mSavedFilesInOrder.remove(usf); 572 mSavedFilesInOrder.add(usf); 573 } 574 }, false /* traverseTopToBottom */); 575 } 576 577 @Override onAppRemoved(ActivityRecord activity)578 void onAppRemoved(ActivityRecord activity) { 579 if (shouldDisableSnapshots()) { 580 return; 581 } 582 removeIfUserSavedFileExist(activity); 583 if (DEBUG) { 584 Slog.d(TAG, "ActivitySnapshotController#onAppRemoved delete snapshot " + activity); 585 } 586 } 587 588 @Override onAppDied(ActivityRecord activity)589 void onAppDied(ActivityRecord activity) { 590 if (shouldDisableSnapshots()) { 591 return; 592 } 593 removeIfUserSavedFileExist(activity); 594 if (DEBUG) { 595 Slog.d(TAG, "ActivitySnapshotController#onAppDied delete snapshot " + activity); 596 } 597 } 598 599 @Override getTopActivity(ActivityRecord activity)600 ActivityRecord getTopActivity(ActivityRecord activity) { 601 return activity; 602 } 603 604 @Override getTaskDescription(ActivityRecord object)605 ActivityManager.TaskDescription getTaskDescription(ActivityRecord object) { 606 return object.taskDescription; 607 } 608 609 /** 610 * Find the window for a given activity to take a snapshot. During app transitions, trampoline 611 * activities can appear in the children, but should be ignored. 612 */ 613 @Override findAppTokenForSnapshot(ActivityRecord activity)614 protected ActivityRecord findAppTokenForSnapshot(ActivityRecord activity) { 615 if (activity == null) { 616 return null; 617 } 618 return activity.canCaptureSnapshot() ? activity : null; 619 } 620 621 @Override use16BitFormat()622 protected boolean use16BitFormat() { 623 return mPersistInfoProvider.use16BitFormat(); 624 } 625 626 @Override getLetterboxInsets(ActivityRecord topActivity)627 protected Rect getLetterboxInsets(ActivityRecord topActivity) { 628 // Do not capture letterbox for ActivityRecord 629 return Letterbox.EMPTY_RECT; 630 } 631 632 @NonNull getUserFiles(int userId)633 private SparseArray<UserSavedFile> getUserFiles(int userId) { 634 if (mUserSavedFiles.get(userId) == null) { 635 mUserSavedFiles.put(userId, new SparseArray<>()); 636 // This is the first time this user attempt to access snapshot, clear up the disk. 637 cleanUpUserFiles(userId); 638 } 639 return mUserSavedFiles.get(userId); 640 } 641 findSavedFile(@onNull ActivityRecord ar)642 UserSavedFile findSavedFile(@NonNull ActivityRecord ar) { 643 final int code = getSystemHashCode(ar); 644 return findSavedFile(ar.mUserId, code); 645 } 646 findSavedFile(int userId, int code)647 UserSavedFile findSavedFile(int userId, int code) { 648 final SparseArray<UserSavedFile> usfs = getUserFiles(userId); 649 return usfs.get(code); 650 } 651 removeCachedFiles(ActivityRecord ar)652 private void removeCachedFiles(ActivityRecord ar) { 653 final UserSavedFile usf = findSavedFile(ar); 654 if (usf != null) { 655 for (int i = usf.mActivityIds.size() - 1; i >= 0; --i) { 656 final int activityId = usf.mActivityIds.get(i); 657 mCache.onIdRemoved(activityId); 658 } 659 } 660 } 661 removeIfUserSavedFileExist(ActivityRecord ar)662 private void removeIfUserSavedFileExist(ActivityRecord ar) { 663 final UserSavedFile usf = findSavedFile(ar); 664 if (usf != null) { 665 final SparseArray<UserSavedFile> usfs = getUserFiles(ar.mUserId); 666 for (int i = usf.mActivityIds.size() - 1; i >= 0; --i) { 667 final int activityId = usf.mActivityIds.get(i); 668 usf.remove(activityId); 669 mCache.onIdRemoved(activityId); 670 usfs.remove(activityId); 671 } 672 mSavedFilesInOrder.remove(usf); 673 mPersister.removeSnapshot(usf.mFileId, ar.mUserId); 674 } 675 } 676 677 @VisibleForTesting hasRecord(@onNull ActivityRecord ar)678 boolean hasRecord(@NonNull ActivityRecord ar) { 679 return findSavedFile(ar) != null; 680 } 681 682 @VisibleForTesting addUserSavedFile(int userId, TaskSnapshot snapshot, @NonNull int[] code)683 void addUserSavedFile(int userId, TaskSnapshot snapshot, @NonNull int[] code) { 684 final UserSavedFile savedFile = findSavedFile(userId, code[0]); 685 if (savedFile != null) { 686 Slog.w(TAG, "Duplicate request for recording activity snapshot " + savedFile); 687 return; 688 } 689 int fileId = 0; 690 for (int i = code.length - 1; i >= 0; --i) { 691 fileId ^= code[i]; 692 } 693 final UserSavedFile usf = new UserSavedFile(fileId, userId); 694 SparseArray<UserSavedFile> usfs = getUserFiles(userId); 695 for (int i = code.length - 1; i >= 0; --i) { 696 usfs.put(code[i], usf); 697 } 698 usf.mActivityIds.addAll(code); 699 mSavedFilesInOrder.add(usf); 700 mPersister.persistSnapshot(fileId, userId, snapshot); 701 702 if (mSavedFilesInOrder.size() > MAX_PERSIST_SNAPSHOT_COUNT * 2) { 703 purgeSavedFile(); 704 } 705 } 706 purgeSavedFile()707 private void purgeSavedFile() { 708 final int savedFileCount = mSavedFilesInOrder.size(); 709 final int removeCount = savedFileCount - MAX_PERSIST_SNAPSHOT_COUNT; 710 if (removeCount < 1) { 711 return; 712 } 713 714 final ArrayList<UserSavedFile> removeTargets = new ArrayList<>(); 715 for (int i = removeCount - 1; i >= 0; --i) { 716 final UserSavedFile usf = mSavedFilesInOrder.remove(i); 717 final SparseArray<UserSavedFile> files = mUserSavedFiles.get(usf.mUserId); 718 for (int j = usf.mActivityIds.size() - 1; j >= 0; --j) { 719 mCache.removeRunningEntry(usf.mActivityIds.get(j)); 720 files.remove(usf.mActivityIds.get(j)); 721 } 722 removeTargets.add(usf); 723 } 724 for (int i = removeTargets.size() - 1; i >= 0; --i) { 725 final UserSavedFile usf = removeTargets.get(i); 726 mPersister.removeSnapshot(usf.mFileId, usf.mUserId); 727 } 728 } 729 730 @Override dump(PrintWriter pw, String prefix)731 void dump(PrintWriter pw, String prefix) { 732 super.dump(pw, prefix); 733 final String doublePrefix = prefix + " "; 734 final String triplePrefix = doublePrefix + " "; 735 for (int i = mUserSavedFiles.size() - 1; i >= 0; --i) { 736 final SparseArray<UserSavedFile> usfs = mUserSavedFiles.valueAt(i); 737 pw.println(doublePrefix + "UserSavedFile userId=" + mUserSavedFiles.keyAt(i)); 738 final ArraySet<UserSavedFile> sets = new ArraySet<>(); 739 for (int j = usfs.size() - 1; j >= 0; --j) { 740 sets.add(usfs.valueAt(j)); 741 } 742 for (int j = sets.size() - 1; j >= 0; --j) { 743 pw.println(triplePrefix + "SavedFile=" + sets.valueAt(j)); 744 } 745 } 746 } 747 748 static class UserSavedFile { 749 // The unique id as filename. 750 final int mFileId; 751 final int mUserId; 752 753 /** 754 * The Id of all activities which are includes in the snapshot. 755 */ 756 final IntArray mActivityIds = new IntArray(); 757 UserSavedFile(int fileId, int userId)758 UserSavedFile(int fileId, int userId) { 759 mFileId = fileId; 760 mUserId = userId; 761 } 762 contains(int code)763 boolean contains(int code) { 764 return mActivityIds.contains(code); 765 } 766 remove(int code)767 void remove(int code) { 768 final int index = mActivityIds.indexOf(code); 769 if (index >= 0) { 770 mActivityIds.remove(index); 771 } 772 } 773 filterExistActivities( @onNull ArraySet<ActivityRecord> pendingLoadActivity)774 ActivityRecord[] filterExistActivities( 775 @NonNull ArraySet<ActivityRecord> pendingLoadActivity) { 776 ArrayList<ActivityRecord> matchedActivities = null; 777 for (int i = pendingLoadActivity.size() - 1; i >= 0; --i) { 778 final ActivityRecord ar = pendingLoadActivity.valueAt(i); 779 if (contains(getSystemHashCode(ar))) { 780 if (matchedActivities == null) { 781 matchedActivities = new ArrayList<>(); 782 } 783 matchedActivities.add(ar); 784 } 785 } 786 if (matchedActivities == null || matchedActivities.size() != mActivityIds.size()) { 787 return null; 788 } 789 return matchedActivities.toArray(new ActivityRecord[0]); 790 } 791 792 @Override toString()793 public String toString() { 794 StringBuilder sb = new StringBuilder(128); 795 sb.append("UserSavedFile{"); 796 sb.append(Integer.toHexString(System.identityHashCode(this))); 797 sb.append(" fileId="); 798 sb.append(Integer.toHexString(mFileId)); 799 sb.append(", activityIds=["); 800 for (int i = mActivityIds.size() - 1; i >= 0; --i) { 801 sb.append(Integer.toHexString(mActivityIds.get(i))); 802 if (i > 0) { 803 sb.append(','); 804 } 805 } 806 sb.append("]"); 807 sb.append("}"); 808 return sb.toString(); 809 } 810 } 811 } 812