1 /*
2  * Copyright (C) 2016 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 android.annotation.Nullable;
20 import android.app.ActivityManager.TaskSnapshot;
21 import android.util.ArrayMap;
22 
23 import java.io.PrintWriter;
24 
25 /**
26  * Caches snapshots. See {@link TaskSnapshotController}.
27  * <p>
28  * Access to this class should be guarded by the global window manager lock.
29  */
30 class TaskSnapshotCache {
31 
32     private final WindowManagerService mService;
33     private final TaskSnapshotLoader mLoader;
34     private final ArrayMap<ActivityRecord, Integer> mAppTaskMap = new ArrayMap<>();
35     private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
36 
TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader)37     TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
38         mService = service;
39         mLoader = loader;
40     }
41 
clearRunningCache()42     void clearRunningCache() {
43         mRunningCache.clear();
44     }
45 
putSnapshot(Task task, TaskSnapshot snapshot)46     void putSnapshot(Task task, TaskSnapshot snapshot) {
47         final CacheEntry entry = mRunningCache.get(task.mTaskId);
48         if (entry != null) {
49             mAppTaskMap.remove(entry.topApp);
50         }
51         final ActivityRecord top = task.getTopMostActivity();
52         mAppTaskMap.put(top, task.mTaskId);
53         mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top));
54     }
55 
56     /**
57      * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
58      */
getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean isLowResolution)59     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
60             boolean isLowResolution) {
61 
62         synchronized (mService.mGlobalLock) {
63             // Try the running cache.
64             final CacheEntry entry = mRunningCache.get(taskId);
65             if (entry != null) {
66                 return entry.snapshot;
67             }
68         }
69 
70         // Try to restore from disk if asked.
71         if (!restoreFromDisk) {
72             return null;
73         }
74         return tryRestoreFromDisk(taskId, userId, isLowResolution);
75     }
76 
77     /**
78      * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
79      */
tryRestoreFromDisk(int taskId, int userId, boolean isLowResolution)80     private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean isLowResolution) {
81         final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, isLowResolution);
82         if (snapshot == null) {
83             return null;
84         }
85         return snapshot;
86     }
87 
88     /**
89      * Called when an app token has been removed
90      */
onAppRemoved(ActivityRecord activity)91     void onAppRemoved(ActivityRecord activity) {
92         final Integer taskId = mAppTaskMap.get(activity);
93         if (taskId != null) {
94             removeRunningEntry(taskId);
95         }
96     }
97 
98     /**
99      * Callend when an app window token's process died.
100      */
onAppDied(ActivityRecord activity)101     void onAppDied(ActivityRecord activity) {
102         final Integer taskId = mAppTaskMap.get(activity);
103         if (taskId != null) {
104             removeRunningEntry(taskId);
105         }
106     }
107 
onTaskRemoved(int taskId)108     void onTaskRemoved(int taskId) {
109         removeRunningEntry(taskId);
110     }
111 
removeRunningEntry(int taskId)112     void removeRunningEntry(int taskId) {
113         final CacheEntry entry = mRunningCache.get(taskId);
114         if (entry != null) {
115             mAppTaskMap.remove(entry.topApp);
116             mRunningCache.remove(taskId);
117         }
118     }
119 
dump(PrintWriter pw, String prefix)120     void dump(PrintWriter pw, String prefix) {
121         final String doublePrefix = prefix + "  ";
122         final String triplePrefix = doublePrefix + "  ";
123         pw.println(prefix + "SnapshotCache");
124         for (int i = mRunningCache.size() - 1; i >= 0; i--) {
125             final CacheEntry entry = mRunningCache.valueAt(i);
126             pw.println(doublePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
127             pw.println(triplePrefix + "topApp=" + entry.topApp);
128             pw.println(triplePrefix + "snapshot=" + entry.snapshot);
129         }
130     }
131 
132     private static final class CacheEntry {
133 
134         /** The snapshot. */
135         final TaskSnapshot snapshot;
136 
137         /** The app token that was on top of the task when the snapshot was taken */
138         final ActivityRecord topApp;
139 
CacheEntry(TaskSnapshot snapshot, ActivityRecord topApp)140         CacheEntry(TaskSnapshot snapshot, ActivityRecord topApp) {
141             this.snapshot = snapshot;
142             this.topApp = topApp;
143         }
144     }
145 }
146