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