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