• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.quickstep.TaskUtils.checkCurrentOrManagedUserId;
22 
23 import android.annotation.TargetApi;
24 import android.app.ActivityManager;
25 import android.app.KeyguardManager;
26 import android.content.ComponentCallbacks2;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.os.Build;
30 import android.os.Process;
31 import android.os.UserHandle;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.launcher3.icons.IconProvider;
36 import com.android.launcher3.icons.IconProvider.IconChangeListener;
37 import com.android.launcher3.util.Executors.SimpleThreadFactory;
38 import com.android.launcher3.util.MainThreadInitializedObject;
39 import com.android.quickstep.util.GroupTask;
40 import com.android.quickstep.util.TaskVisualsChangeListener;
41 import com.android.systemui.shared.recents.model.Task;
42 import com.android.systemui.shared.recents.model.ThumbnailData;
43 import com.android.systemui.shared.system.ActivityManagerWrapper;
44 import com.android.systemui.shared.system.TaskStackChangeListener;
45 import com.android.systemui.shared.system.TaskStackChangeListeners;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.concurrent.Executor;
51 import java.util.concurrent.Executors;
52 import java.util.function.Consumer;
53 import java.util.function.Predicate;
54 
55 /**
56  * Singleton class to load and manage recents model.
57  */
58 @TargetApi(Build.VERSION_CODES.O)
59 public class RecentsModel implements IconChangeListener, TaskStackChangeListener,
60         TaskVisualsChangeListener {
61 
62     // We do not need any synchronization for this variable as its only written on UI thread.
63     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
64             new MainThreadInitializedObject<>(RecentsModel::new);
65 
66     private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
67             new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
68 
69     private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>();
70     private final Context mContext;
71 
72     private final RecentTasksList mTaskList;
73     private final TaskIconCache mIconCache;
74     private final TaskThumbnailCache mThumbnailCache;
75 
RecentsModel(Context context)76     private RecentsModel(Context context) {
77         mContext = context;
78         mTaskList = new RecentTasksList(MAIN_EXECUTOR,
79                 context.getSystemService(KeyguardManager.class),
80                 SystemUiProxy.INSTANCE.get(context));
81 
82         IconProvider iconProvider = new IconProvider(context);
83         mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
84         mIconCache.registerTaskVisualsChangeListener(this);
85         mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR);
86 
87         TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
88         iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler());
89     }
90 
getIconCache()91     public TaskIconCache getIconCache() {
92         return mIconCache;
93     }
94 
getThumbnailCache()95     public TaskThumbnailCache getThumbnailCache() {
96         return mThumbnailCache;
97     }
98 
99     /**
100      * Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks
101      * at the end of the list.
102      *
103      * @param callback The callback to receive the task plan once its complete or null. This is
104      *                always called on the UI thread.
105      * @return the request id associated with this call.
106      */
getTasks(Consumer<ArrayList<GroupTask>> callback)107     public int getTasks(Consumer<ArrayList<GroupTask>> callback) {
108         return mTaskList.getTasks(false /* loadKeysOnly */, callback,
109                 RecentsFilterState.DEFAULT_FILTER);
110     }
111 
112 
113     /**
114      * Fetches the list of recent tasks, based on a filter
115      *
116      * @param callback The callback to receive the task plan once its complete or null. This is
117      *                always called on the UI thread.
118      * @param filter  Returns true if a GroupTask should be included into the list passed into
119      *                callback.
120      * @return the request id associated with this call.
121      */
getTasks(Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter)122     public int getTasks(Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
123         return mTaskList.getTasks(false /* loadKeysOnly */, callback, filter);
124     }
125 
126     /**
127      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
128      */
isTaskListValid(int changeId)129     public boolean isTaskListValid(int changeId) {
130         return mTaskList.isTaskListValid(changeId);
131     }
132 
133     /**
134      * @return Whether the task list is currently updating in the background
135      */
136     @VisibleForTesting
isLoadingTasksInBackground()137     public boolean isLoadingTasksInBackground() {
138         return mTaskList.isLoadingTasksInBackground();
139     }
140 
141     /**
142      * Checks if a task has been removed or not.
143      *
144      * @param callback Receives true if task is removed, false otherwise
145      * @param filter Returns true if GroupTask should be in the list of considerations
146      */
isTaskRemoved(int taskId, Consumer<Boolean> callback, Predicate<GroupTask> filter)147     public void isTaskRemoved(int taskId, Consumer<Boolean> callback, Predicate<GroupTask> filter) {
148         // Invalidate the existing list before checking to ensure this reflects the current state in
149         // the system
150         mTaskList.onRecentTasksChanged();
151         mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
152             for (GroupTask group : taskGroups) {
153                 if (group.containsTask(taskId)) {
154                     callback.accept(false);
155                     return;
156                 }
157             }
158             callback.accept(true);
159         }, filter);
160     }
161 
162     @Override
onTaskStackChangedBackground()163     public void onTaskStackChangedBackground() {
164         if (!mThumbnailCache.isPreloadingEnabled()) {
165             // Skip if we aren't preloading
166             return;
167         }
168 
169         int currentUserId = Process.myUserHandle().getIdentifier();
170         if (!checkCurrentOrManagedUserId(currentUserId, mContext)) {
171             // Skip if we are not the current user
172             return;
173         }
174 
175         // Keep the cache up to date with the latest thumbnails
176         ActivityManager.RunningTaskInfo runningTask =
177                 ActivityManagerWrapper.getInstance().getRunningTask();
178         int runningTaskId = runningTask != null ? runningTask.id : -1;
179         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
180             for (GroupTask group : taskGroups) {
181                 if (group.containsTask(runningTaskId)) {
182                     // Skip the running task, it's not going to have an up-to-date snapshot by the
183                     // time the user next enters overview
184                     continue;
185                 }
186                 mThumbnailCache.updateThumbnailInCache(group.task1);
187                 mThumbnailCache.updateThumbnailInCache(group.task2);
188             }
189         });
190     }
191 
192     @Override
onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)193     public boolean onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
194         mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
195 
196         for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
197             Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);
198             if (task != null) {
199                 task.thumbnail = snapshot;
200             }
201         }
202         return true;
203     }
204 
205     @Override
onTaskRemoved(int taskId)206     public void onTaskRemoved(int taskId) {
207         Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0);
208         mThumbnailCache.remove(stubKey);
209         mIconCache.onTaskRemoved(stubKey);
210     }
211 
onTrimMemory(int level)212     public void onTrimMemory(int level) {
213         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
214             mThumbnailCache.getHighResLoadingState().setVisible(false);
215         }
216         if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
217             // Clear everything once we reach a low-mem situation
218             mThumbnailCache.clear();
219             mIconCache.clearCache();
220         }
221     }
222 
223     @Override
onAppIconChanged(String packageName, UserHandle user)224     public void onAppIconChanged(String packageName, UserHandle user) {
225         mIconCache.invalidateCacheEntries(packageName, user);
226         for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
227             mThumbnailChangeListeners.get(i).onTaskIconChanged(packageName, user);
228         }
229     }
230 
231     @Override
onTaskIconChanged(int taskId)232     public void onTaskIconChanged(int taskId) {
233         for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
234             listener.onTaskIconChanged(taskId);
235         }
236     }
237 
238     @Override
onSystemIconStateChanged(String iconState)239     public void onSystemIconStateChanged(String iconState) {
240         mIconCache.clearCache();
241     }
242 
243     /**
244      * Adds a listener for visuals changes
245      */
addThumbnailChangeListener(TaskVisualsChangeListener listener)246     public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
247         mThumbnailChangeListeners.add(listener);
248     }
249 
250     /**
251      * Removes a previously added listener
252      */
removeThumbnailChangeListener(TaskVisualsChangeListener listener)253     public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
254         mThumbnailChangeListeners.remove(listener);
255     }
256 
dump(String prefix, PrintWriter writer)257     public void dump(String prefix, PrintWriter writer) {
258         writer.println(prefix + "RecentsModel:");
259         mTaskList.dump("  ", writer);
260     }
261 
262     /**
263      * Registers a listener for running tasks
264      */
registerRunningTasksListener(RunningTasksListener listener)265     public void registerRunningTasksListener(RunningTasksListener listener) {
266         mTaskList.registerRunningTasksListener(listener);
267     }
268 
269     /**
270      * Removes the previously registered running tasks listener
271      */
unregisterRunningTasksListener()272     public void unregisterRunningTasksListener() {
273         mTaskList.unregisterRunningTasksListener();
274     }
275 
276     /**
277      * Gets the set of running tasks.
278      */
getRunningTasks()279     public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
280         return mTaskList.getRunningTasks();
281     }
282 
283     /**
284      * Listener for receiving running tasks changes
285      */
286     public interface RunningTasksListener {
287         /**
288          * Called when there's a change to running tasks
289          */
onRunningTasksChanged()290         void onRunningTasksChanged();
291     }
292 }
293