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