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