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