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.RemoteException; 32 import android.os.UserHandle; 33 import android.util.Log; 34 35 import com.android.launcher3.compat.LauncherAppsCompat; 36 import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat; 37 import com.android.launcher3.util.MainThreadInitializedObject; 38 import com.android.systemui.shared.recents.ISystemUiProxy; 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 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.function.Consumer; 48 49 /** 50 * Singleton class to load and manage recents model. 51 */ 52 @TargetApi(Build.VERSION_CODES.O) 53 public class RecentsModel extends TaskStackChangeListener { 54 55 private static final String TAG = "RecentsModel"; 56 57 // We do not need any synchronization for this variable as its only written on UI thread. 58 public static final MainThreadInitializedObject<RecentsModel> INSTANCE = 59 new MainThreadInitializedObject<>(RecentsModel::new); 60 61 private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>(); 62 private final Context mContext; 63 64 private ISystemUiProxy mSystemUiProxy; 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 Looper looper = 73 createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND); 74 mTaskList = new RecentTasksList(MAIN_EXECUTOR, 75 new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); 76 mIconCache = new TaskIconCache(context, looper); 77 mThumbnailCache = new TaskThumbnailCache(context, looper); 78 ActivityManagerWrapper.getInstance().registerTaskStackListener(this); 79 setupPackageListener(); 80 } 81 getIconCache()82 public TaskIconCache getIconCache() { 83 return mIconCache; 84 } 85 getThumbnailCache()86 public TaskThumbnailCache getThumbnailCache() { 87 return mThumbnailCache; 88 } 89 90 /** 91 * Fetches the list of recent tasks. 92 * 93 * @param callback The callback to receive the task plan once its complete or null. This is 94 * always called on the UI thread. 95 * @return the request id associated with this call. 96 */ getTasks(Consumer<ArrayList<Task>> callback)97 public int getTasks(Consumer<ArrayList<Task>> callback) { 98 return mTaskList.getTasks(false /* loadKeysOnly */, callback); 99 } 100 101 /** 102 * @return The task id of the running task, or -1 if there is no current running task. 103 */ getRunningTaskId()104 public static int getRunningTaskId() { 105 ActivityManager.RunningTaskInfo runningTask = 106 ActivityManagerWrapper.getInstance().getRunningTask(); 107 return runningTask != null ? runningTask.id : -1; 108 } 109 110 /** 111 * @return Whether the provided {@param changeId} is the latest recent tasks list id. 112 */ isTaskListValid(int changeId)113 public boolean isTaskListValid(int changeId) { 114 return mTaskList.isTaskListValid(changeId); 115 } 116 117 /** 118 * Finds and returns the task key associated with the given task id. 119 * 120 * @param callback The callback to receive the task key if it is found or null. This is always 121 * called on the UI thread. 122 */ findTaskWithId(int taskId, Consumer<Task.TaskKey> callback)123 public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) { 124 mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> { 125 for (Task task : tasks) { 126 if (task.key.id == taskId) { 127 callback.accept(task.key); 128 return; 129 } 130 } 131 callback.accept(null); 132 }); 133 } 134 135 @Override onTaskStackChangedBackground()136 public void onTaskStackChangedBackground() { 137 if (!mThumbnailCache.isPreloadingEnabled()) { 138 // Skip if we aren't preloading 139 return; 140 } 141 142 int currentUserId = Process.myUserHandle().getIdentifier(); 143 if (!checkCurrentOrManagedUserId(currentUserId, mContext)) { 144 // Skip if we are not the current user 145 return; 146 } 147 148 // Keep the cache up to date with the latest thumbnails 149 int runningTaskId = RecentsModel.getRunningTaskId(); 150 mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> { 151 for (Task task : tasks) { 152 if (task.key.id == runningTaskId) { 153 // Skip the running task, it's not going to have an up-to-date snapshot by the 154 // time the user next enters overview 155 continue; 156 } 157 mThumbnailCache.updateThumbnailInCache(task); 158 } 159 }); 160 } 161 162 @Override onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)163 public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { 164 mThumbnailCache.updateTaskSnapShot(taskId, snapshot); 165 166 for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { 167 Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot); 168 if (task != null) { 169 task.thumbnail = snapshot; 170 } 171 } 172 } 173 174 @Override onTaskRemoved(int taskId)175 public void onTaskRemoved(int taskId) { 176 Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0); 177 mThumbnailCache.remove(dummyKey); 178 mIconCache.onTaskRemoved(dummyKey); 179 } 180 setSystemUiProxy(ISystemUiProxy systemUiProxy)181 public void setSystemUiProxy(ISystemUiProxy systemUiProxy) { 182 mSystemUiProxy = systemUiProxy; 183 } 184 getSystemUiProxy()185 public ISystemUiProxy getSystemUiProxy() { 186 return mSystemUiProxy; 187 } 188 onTrimMemory(int level)189 public void onTrimMemory(int level) { 190 if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 191 mThumbnailCache.getHighResLoadingState().setVisible(false); 192 } 193 if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { 194 // Clear everything once we reach a low-mem situation 195 mThumbnailCache.clear(); 196 mIconCache.clear(); 197 } 198 } 199 onOverviewShown(boolean fromHome, String tag)200 public void onOverviewShown(boolean fromHome, String tag) { 201 if (mSystemUiProxy == null) { 202 return; 203 } 204 try { 205 mSystemUiProxy.onOverviewShown(fromHome); 206 } catch (RemoteException e) { 207 Log.w(tag, 208 "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app") 209 + ": ", e); 210 } 211 } 212 setupPackageListener()213 private void setupPackageListener() { 214 LauncherAppsCompat.getInstance(mContext) 215 .addOnAppsChangedCallback(new OnAppsChangedCallbackCompat() { 216 @Override 217 public void onPackageRemoved(String packageName, UserHandle user) { 218 mIconCache.invalidatePackage(packageName); 219 } 220 221 @Override 222 public void onPackageChanged(String packageName, UserHandle user) { 223 mIconCache.invalidatePackage(packageName); 224 } 225 }); 226 } 227 addThumbnailChangeListener(TaskThumbnailChangeListener listener)228 public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) { 229 mThumbnailChangeListeners.add(listener); 230 } 231 removeThumbnailChangeListener(TaskThumbnailChangeListener listener)232 public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) { 233 mThumbnailChangeListeners.remove(listener); 234 } 235 236 public interface TaskThumbnailChangeListener { 237 onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)238 Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); 239 } 240 } 241