1 /*
2  * Copyright (C) 2018 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 package com.android.quickstep;
17 
18 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
19 
20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
21 import static com.android.launcher3.util.Executors.createAndStartNewLooper;
22 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
23 
24 import android.annotation.TargetApi;
25 import android.app.ActivityManager;
26 import android.content.ComponentCallbacks2;
27 import android.content.Context;
28 import android.os.Build;
29 import android.os.Looper;
30 import android.os.Process;
31 import android.os.UserHandle;
32 
33 import com.android.launcher3.icons.IconProvider;
34 import com.android.launcher3.util.MainThreadInitializedObject;
35 import com.android.systemui.shared.recents.model.Task;
36 import com.android.systemui.shared.recents.model.ThumbnailData;
37 import com.android.systemui.shared.system.ActivityManagerWrapper;
38 import com.android.systemui.shared.system.KeyguardManagerCompat;
39 import com.android.systemui.shared.system.TaskStackChangeListener;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.function.Consumer;
44 
45 /**
46  * Singleton class to load and manage recents model.
47  */
48 @TargetApi(Build.VERSION_CODES.O)
49 public class RecentsModel extends TaskStackChangeListener {
50 
51     // We do not need any synchronization for this variable as its only written on UI thread.
52     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
53             new MainThreadInitializedObject<>(RecentsModel::new);
54 
55     private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>();
56     private final Context mContext;
57 
58     private final RecentTasksList mTaskList;
59     private final TaskIconCache mIconCache;
60     private final TaskThumbnailCache mThumbnailCache;
61 
RecentsModel(Context context)62     private RecentsModel(Context context) {
63         mContext = context;
64         Looper looper =
65                 createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND);
66         mTaskList = new RecentTasksList(MAIN_EXECUTOR,
67                 new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
68         mIconCache = new TaskIconCache(context, looper);
69         mThumbnailCache = new TaskThumbnailCache(context, looper);
70 
71         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
72         IconProvider.registerIconChangeListener(context,
73                 this::onPackageIconChanged, MAIN_EXECUTOR.getHandler());
74     }
75 
getIconCache()76     public TaskIconCache getIconCache() {
77         return mIconCache;
78     }
79 
getThumbnailCache()80     public TaskThumbnailCache getThumbnailCache() {
81         return mThumbnailCache;
82     }
83 
84     /**
85      * Fetches the list of recent tasks.
86      *
87      * @param callback The callback to receive the task plan once its complete or null. This is
88      *                always called on the UI thread.
89      * @return the request id associated with this call.
90      */
getTasks(Consumer<ArrayList<Task>> callback)91     public int getTasks(Consumer<ArrayList<Task>> callback) {
92         return mTaskList.getTasks(false /* loadKeysOnly */, callback);
93     }
94 
95     /**
96      * @return The task id of the running task, or -1 if there is no current running task.
97      */
getRunningTaskId()98     public static int getRunningTaskId() {
99         ActivityManager.RunningTaskInfo runningTask =
100                 ActivityManagerWrapper.getInstance().getRunningTask();
101         return runningTask != null ? runningTask.id : -1;
102     }
103 
104     /**
105      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
106      */
isTaskListValid(int changeId)107     public boolean isTaskListValid(int changeId) {
108         return mTaskList.isTaskListValid(changeId);
109     }
110 
111     /**
112      * Finds and returns the task key associated with the given task id.
113      *
114      * @param callback The callback to receive the task key if it is found or null. This is always
115      *                 called on the UI thread.
116      */
findTaskWithId(int taskId, Consumer<Task.TaskKey> callback)117     public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) {
118         mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
119             for (Task task : tasks) {
120                 if (task.key.id == taskId) {
121                     callback.accept(task.key);
122                     return;
123                 }
124             }
125             callback.accept(null);
126         });
127     }
128 
129     @Override
onTaskStackChangedBackground()130     public void onTaskStackChangedBackground() {
131         if (!mThumbnailCache.isPreloadingEnabled()) {
132             // Skip if we aren't preloading
133             return;
134         }
135 
136         int currentUserId = Process.myUserHandle().getIdentifier();
137         if (!checkCurrentOrManagedUserId(currentUserId, mContext)) {
138             // Skip if we are not the current user
139             return;
140         }
141 
142         // Keep the cache up to date with the latest thumbnails
143         int runningTaskId = RecentsModel.getRunningTaskId();
144         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
145             for (Task task : tasks) {
146                 if (task.key.id == runningTaskId) {
147                     // Skip the running task, it's not going to have an up-to-date snapshot by the
148                     // time the user next enters overview
149                     continue;
150                 }
151                 mThumbnailCache.updateThumbnailInCache(task);
152             }
153         });
154     }
155 
156     @Override
onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)157     public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
158         mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
159 
160         for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
161             Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);
162             if (task != null) {
163                 task.thumbnail = snapshot;
164             }
165         }
166     }
167 
168     @Override
onTaskRemoved(int taskId)169     public void onTaskRemoved(int taskId) {
170         Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
171         mThumbnailCache.remove(dummyKey);
172         mIconCache.onTaskRemoved(dummyKey);
173     }
174 
onTrimMemory(int level)175     public void onTrimMemory(int level) {
176         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
177             mThumbnailCache.getHighResLoadingState().setVisible(false);
178         }
179         if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
180             // Clear everything once we reach a low-mem situation
181             mThumbnailCache.clear();
182             mIconCache.clear();
183         }
184     }
185 
onPackageIconChanged(String pkg, UserHandle user)186     private void onPackageIconChanged(String pkg, UserHandle user) {
187         mIconCache.invalidateCacheEntries(pkg, user);
188         for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
189             mThumbnailChangeListeners.get(i).onTaskIconChanged(pkg, user);
190         }
191     }
192 
193     /**
194      * Adds a listener for visuals changes
195      */
addThumbnailChangeListener(TaskVisualsChangeListener listener)196     public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
197         mThumbnailChangeListeners.add(listener);
198     }
199 
200     /**
201      * Removes a previously added listener
202      */
removeThumbnailChangeListener(TaskVisualsChangeListener listener)203     public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
204         mThumbnailChangeListeners.remove(listener);
205     }
206 
207     /**
208      * Listener for receiving various task properties changes
209      */
210     public interface TaskVisualsChangeListener {
211 
212         /**
213          * Called whn the task thumbnail changes
214          */
onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)215         Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
216 
217         /**
218          * Called when the icon for a task changes
219          */
onTaskIconChanged(String pkg, UserHandle user)220         void onTaskIconChanged(String pkg, UserHandle user);
221     }
222 }
223