• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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