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