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