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 mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> { 149 for (GroupTask group : taskGroups) { 150 if (group.containsTask(taskId)) { 151 callback.accept(false); 152 return; 153 } 154 } 155 callback.accept(true); 156 }, filter); 157 } 158 159 @Override onTaskStackChangedBackground()160 public void onTaskStackChangedBackground() { 161 if (!mThumbnailCache.isPreloadingEnabled()) { 162 // Skip if we aren't preloading 163 return; 164 } 165 166 int currentUserId = Process.myUserHandle().getIdentifier(); 167 if (!checkCurrentOrManagedUserId(currentUserId, mContext)) { 168 // Skip if we are not the current user 169 return; 170 } 171 172 // Keep the cache up to date with the latest thumbnails 173 ActivityManager.RunningTaskInfo runningTask = 174 ActivityManagerWrapper.getInstance().getRunningTask(); 175 int runningTaskId = runningTask != null ? runningTask.id : -1; 176 mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> { 177 for (GroupTask group : taskGroups) { 178 if (group.containsTask(runningTaskId)) { 179 // Skip the running task, it's not going to have an up-to-date snapshot by the 180 // time the user next enters overview 181 continue; 182 } 183 mThumbnailCache.updateThumbnailInCache(group.task1); 184 mThumbnailCache.updateThumbnailInCache(group.task2); 185 } 186 }); 187 } 188 189 @Override onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)190 public boolean onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { 191 mThumbnailCache.updateTaskSnapShot(taskId, snapshot); 192 193 for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { 194 Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot); 195 if (task != null) { 196 task.thumbnail = snapshot; 197 } 198 } 199 return true; 200 } 201 202 @Override onTaskRemoved(int taskId)203 public void onTaskRemoved(int taskId) { 204 Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0); 205 mThumbnailCache.remove(stubKey); 206 mIconCache.onTaskRemoved(stubKey); 207 } 208 onTrimMemory(int level)209 public void onTrimMemory(int level) { 210 if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 211 mThumbnailCache.getHighResLoadingState().setVisible(false); 212 } 213 if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { 214 // Clear everything once we reach a low-mem situation 215 mThumbnailCache.clear(); 216 mIconCache.clearCache(); 217 } 218 } 219 220 @Override onAppIconChanged(String packageName, UserHandle user)221 public void onAppIconChanged(String packageName, UserHandle user) { 222 mIconCache.invalidateCacheEntries(packageName, user); 223 for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { 224 mThumbnailChangeListeners.get(i).onTaskIconChanged(packageName, user); 225 } 226 } 227 228 @Override onTaskIconChanged(int taskId)229 public void onTaskIconChanged(int taskId) { 230 for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) { 231 listener.onTaskIconChanged(taskId); 232 } 233 } 234 235 @Override onSystemIconStateChanged(String iconState)236 public void onSystemIconStateChanged(String iconState) { 237 mIconCache.clearCache(); 238 } 239 240 /** 241 * Adds a listener for visuals changes 242 */ addThumbnailChangeListener(TaskVisualsChangeListener listener)243 public void addThumbnailChangeListener(TaskVisualsChangeListener listener) { 244 mThumbnailChangeListeners.add(listener); 245 } 246 247 /** 248 * Removes a previously added listener 249 */ removeThumbnailChangeListener(TaskVisualsChangeListener listener)250 public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) { 251 mThumbnailChangeListeners.remove(listener); 252 } 253 dump(String prefix, PrintWriter writer)254 public void dump(String prefix, PrintWriter writer) { 255 writer.println(prefix + "RecentsModel:"); 256 mTaskList.dump(" ", writer); 257 } 258 259 /** 260 * Registers a listener for running tasks 261 */ registerRunningTasksListener(RunningTasksListener listener)262 public void registerRunningTasksListener(RunningTasksListener listener) { 263 mTaskList.registerRunningTasksListener(listener); 264 } 265 266 /** 267 * Removes the previously registered running tasks listener 268 */ unregisterRunningTasksListener()269 public void unregisterRunningTasksListener() { 270 mTaskList.unregisterRunningTasksListener(); 271 } 272 273 /** 274 * Gets the set of running tasks. 275 */ getRunningTasks()276 public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() { 277 return mTaskList.getRunningTasks(); 278 } 279 280 /** 281 * Listener for receiving running tasks changes 282 */ 283 public interface RunningTasksListener { 284 /** 285 * Called when there's a change to running tasks 286 */ onRunningTasksChanged()287 void onRunningTasksChanged(); 288 } 289 } 290