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