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 com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; 19 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; 20 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; 21 22 import android.annotation.TargetApi; 23 import android.app.ActivityManager; 24 import android.content.ComponentCallbacks2; 25 import android.content.Context; 26 import android.os.Build; 27 import android.os.Bundle; 28 import android.os.HandlerThread; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.util.Log; 32 33 import com.android.launcher3.util.MainThreadInitializedObject; 34 import com.android.systemui.shared.recents.ISystemUiProxy; 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.QuickStepContract; 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 private static final String TAG = "RecentsModel"; 52 53 // We do not need any synchronization for this variable as its only written on UI thread. 54 public static final MainThreadInitializedObject<RecentsModel> INSTANCE = 55 new MainThreadInitializedObject<>(c -> new RecentsModel(c)); 56 57 private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>(); 58 private final Context mContext; 59 60 private ISystemUiProxy mSystemUiProxy; 61 62 private final RecentTasksList mTaskList; 63 private final TaskIconCache mIconCache; 64 private final TaskThumbnailCache mThumbnailCache; 65 RecentsModel(Context context)66 private RecentsModel(Context context) { 67 mContext = context; 68 HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache", 69 Process.THREAD_PRIORITY_BACKGROUND); 70 loaderThread.start(); 71 mTaskList = new RecentTasksList(context); 72 mIconCache = new TaskIconCache(context, loaderThread.getLooper()); 73 mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper()); 74 ActivityManagerWrapper.getInstance().registerTaskStackListener(this); 75 } 76 getIconCache()77 public TaskIconCache getIconCache() { 78 return mIconCache; 79 } 80 getThumbnailCache()81 public TaskThumbnailCache getThumbnailCache() { 82 return mThumbnailCache; 83 } 84 85 /** 86 * Fetches the list of recent tasks. 87 * 88 * @param callback The callback to receive the task plan once its complete or null. This is 89 * always called on the UI thread. 90 * @return the request id associated with this call. 91 */ getTasks(Consumer<ArrayList<Task>> callback)92 public int getTasks(Consumer<ArrayList<Task>> callback) { 93 return mTaskList.getTasks(false /* loadKeysOnly */, callback); 94 } 95 96 /** 97 * @return The task id of the running task, or -1 if there is no current running task. 98 */ getRunningTaskId()99 public static int getRunningTaskId() { 100 ActivityManager.RunningTaskInfo runningTask = 101 ActivityManagerWrapper.getInstance().getRunningTask(); 102 return runningTask != null ? runningTask.id : -1; 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 * Finds and returns the task key associated with the given task id. 114 * 115 * @param callback The callback to receive the task key if it is found or null. This is always 116 * called on the UI thread. 117 */ findTaskWithId(int taskId, Consumer<Task.TaskKey> callback)118 public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) { 119 mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> { 120 for (Task task : tasks) { 121 if (task.key.id == taskId) { 122 callback.accept(task.key); 123 return; 124 } 125 } 126 callback.accept(null); 127 }); 128 } 129 130 @Override onTaskStackChangedBackground()131 public void onTaskStackChangedBackground() { 132 if (!mThumbnailCache.isPreloadingEnabled()) { 133 // Skip if we aren't preloading 134 return; 135 } 136 137 int currentUserId = Process.myUserHandle().getIdentifier(); 138 if (!checkCurrentOrManagedUserId(currentUserId, mContext)) { 139 // Skip if we are not the current user 140 return; 141 } 142 143 // Keep the cache up to date with the latest thumbnails 144 int runningTaskId = RecentsModel.getRunningTaskId(); 145 mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> { 146 for (Task task : tasks) { 147 if (task.key.id == runningTaskId) { 148 // Skip the running task, it's not going to have an up-to-date snapshot by the 149 // time the user next enters overview 150 continue; 151 } 152 mThumbnailCache.updateThumbnailInCache(task); 153 } 154 }); 155 } 156 157 @Override onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)158 public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { 159 mThumbnailCache.updateTaskSnapShot(taskId, snapshot); 160 161 for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { 162 Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot); 163 if (task != null) { 164 task.thumbnail = snapshot; 165 } 166 } 167 } 168 169 @Override onTaskRemoved(int taskId)170 public void onTaskRemoved(int taskId) { 171 Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0); 172 mThumbnailCache.remove(dummyKey); 173 } 174 setSystemUiProxy(ISystemUiProxy systemUiProxy)175 public void setSystemUiProxy(ISystemUiProxy systemUiProxy) { 176 mSystemUiProxy = systemUiProxy; 177 } 178 getSystemUiProxy()179 public ISystemUiProxy getSystemUiProxy() { 180 return mSystemUiProxy; 181 } 182 onTrimMemory(int level)183 public void onTrimMemory(int level) { 184 if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 185 mThumbnailCache.getHighResLoadingState().setVisible(false); 186 } 187 if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { 188 // Clear everything once we reach a low-mem situation 189 mThumbnailCache.clear(); 190 mIconCache.clear(); 191 } 192 } 193 onOverviewShown(boolean fromHome, String tag)194 public void onOverviewShown(boolean fromHome, String tag) { 195 if (mSystemUiProxy == null) { 196 return; 197 } 198 try { 199 mSystemUiProxy.onOverviewShown(fromHome); 200 } catch (RemoteException e) { 201 Log.w(tag, 202 "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app") 203 + ": ", e); 204 } 205 } 206 addThumbnailChangeListener(TaskThumbnailChangeListener listener)207 public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) { 208 mThumbnailChangeListeners.add(listener); 209 } 210 removeThumbnailChangeListener(TaskThumbnailChangeListener listener)211 public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) { 212 mThumbnailChangeListeners.remove(listener); 213 } 214 215 public interface TaskThumbnailChangeListener { 216 onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)217 Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); 218 } 219 } 220