• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Flags.enableGridOnlyOverview;
21 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
22 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
23 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
24 
25 import android.annotation.SuppressLint;
26 import android.annotation.TargetApi;
27 import android.app.ActivityManager;
28 import android.app.KeyguardManager;
29 import android.content.ComponentCallbacks;
30 import android.content.ComponentCallbacks2;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.res.Configuration;
34 import android.os.Build;
35 import android.os.Process;
36 import android.os.UserHandle;
37 
38 import androidx.annotation.Nullable;
39 import androidx.annotation.VisibleForTesting;
40 
41 import com.android.launcher3.dagger.ApplicationContext;
42 import com.android.launcher3.dagger.LauncherAppSingleton;
43 import com.android.launcher3.graphics.ThemeManager;
44 import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
45 import com.android.launcher3.icons.IconProvider;
46 import com.android.launcher3.statehandlers.DesktopVisibilityController;
47 import com.android.launcher3.util.DaggerSingletonObject;
48 import com.android.launcher3.util.DaggerSingletonTracker;
49 import com.android.launcher3.util.DisplayController;
50 import com.android.launcher3.util.Executors.SimpleThreadFactory;
51 import com.android.launcher3.util.LockedUserState;
52 import com.android.launcher3.util.SafeCloseable;
53 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
54 import com.android.quickstep.recents.data.RecentTasksDataSource;
55 import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
56 import com.android.quickstep.util.DesktopTask;
57 import com.android.quickstep.util.GroupTask;
58 import com.android.quickstep.util.TaskVisualsChangeListener;
59 import com.android.systemui.shared.recents.model.Task;
60 import com.android.systemui.shared.recents.model.ThumbnailData;
61 import com.android.systemui.shared.system.ActivityManagerWrapper;
62 import com.android.systemui.shared.system.TaskStackChangeListener;
63 import com.android.systemui.shared.system.TaskStackChangeListeners;
64 
65 import dagger.Lazy;
66 
67 import java.io.PrintWriter;
68 import java.util.ArrayList;
69 import java.util.List;
70 import java.util.concurrent.ConcurrentLinkedQueue;
71 import java.util.concurrent.Executor;
72 import java.util.concurrent.Executors;
73 import java.util.function.Consumer;
74 import java.util.function.Predicate;
75 
76 import javax.inject.Inject;
77 
78 /**
79  * Singleton class to load and manage recents model.
80  */
81 @TargetApi(Build.VERSION_CODES.O)
82 @LauncherAppSingleton
83 public class RecentsModel implements RecentTasksDataSource, TaskStackChangeListener,
84         TaskVisualsChangeListener, TaskVisualsChangeNotifier,
85         ThemeChangeListener {
86 
87     // We do not need any synchronization for this variable as its only written on UI thread.
88     public static final DaggerSingletonObject<RecentsModel> INSTANCE =
89             new DaggerSingletonObject<>(QuickstepBaseAppComponent::getRecentsModel);
90 
91     private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
92             new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
93 
94     private final ConcurrentLinkedQueue<TaskVisualsChangeListener> mThumbnailChangeListeners =
95             new ConcurrentLinkedQueue<>();
96     private final Context mContext;
97     private final RecentTasksList mTaskList;
98     private final TaskIconCache mIconCache;
99     private final TaskThumbnailCache mThumbnailCache;
100 
101     @Inject
RecentsModel(@pplicationContext Context context, SystemUiProxy systemUiProxy, TopTaskTracker topTaskTracker, DisplayController displayController, LockedUserState lockedUserState, Lazy<ThemeManager> themeManagerLazy, DesktopVisibilityController desktopVisibilityController, DaggerSingletonTracker tracker )102      public RecentsModel(@ApplicationContext Context context,
103             SystemUiProxy systemUiProxy,
104             TopTaskTracker topTaskTracker,
105             DisplayController displayController,
106             LockedUserState lockedUserState,
107             Lazy<ThemeManager> themeManagerLazy,
108             DesktopVisibilityController desktopVisibilityController,
109             DaggerSingletonTracker tracker
110             ) {
111         // Lazily inject the ThemeManager and access themeManager once the device is
112         // unlocked. See b/393248495 for details.
113         this(context, new IconProvider(context), systemUiProxy, topTaskTracker,
114                 displayController, lockedUserState, themeManagerLazy, desktopVisibilityController,
115                 tracker);
116     }
117 
118     @SuppressLint("VisibleForTests")
RecentsModel(@pplicationContext Context context, IconProvider iconProvider, SystemUiProxy systemUiProxy, TopTaskTracker topTaskTracker, DisplayController displayController, LockedUserState lockedUserState, Lazy<ThemeManager> themeManagerLazy, DesktopVisibilityController desktopVisibilityController, DaggerSingletonTracker tracker)119     private RecentsModel(@ApplicationContext Context context,
120             IconProvider iconProvider,
121             SystemUiProxy systemUiProxy,
122             TopTaskTracker topTaskTracker,
123             DisplayController displayController,
124             LockedUserState lockedUserState,
125             Lazy<ThemeManager> themeManagerLazy,
126             DesktopVisibilityController desktopVisibilityController,
127             DaggerSingletonTracker tracker) {
128         this(context,
129                 new RecentTasksList(
130                         context,
131                         MAIN_EXECUTOR,
132                         context.getSystemService(KeyguardManager.class),
133                         systemUiProxy,
134                         topTaskTracker, desktopVisibilityController, tracker),
135                 new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider, displayController),
136                 new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
137                 iconProvider,
138                 TaskStackChangeListeners.getInstance(),
139                 lockedUserState,
140                 themeManagerLazy,
141                 tracker);
142     }
143 
144     @VisibleForTesting
RecentsModel(@pplicationContext Context context, RecentTasksList taskList, TaskIconCache iconCache, TaskThumbnailCache thumbnailCache, IconProvider iconProvider, TaskStackChangeListeners taskStackChangeListeners, LockedUserState lockedUserState, Lazy<ThemeManager> themeManagerLazy, DaggerSingletonTracker tracker)145     RecentsModel(@ApplicationContext Context context,
146             RecentTasksList taskList,
147             TaskIconCache iconCache,
148             TaskThumbnailCache thumbnailCache,
149             IconProvider iconProvider,
150             TaskStackChangeListeners taskStackChangeListeners,
151             LockedUserState lockedUserState,
152             Lazy<ThemeManager> themeManagerLazy,
153             DaggerSingletonTracker tracker) {
154         mContext = context;
155         mTaskList = taskList;
156         mIconCache = iconCache;
157         mIconCache.registerTaskVisualsChangeListener(this);
158         mThumbnailCache = thumbnailCache;
159         if (isCachePreloadingEnabled()) {
160             ComponentCallbacks componentCallbacks = new ComponentCallbacks() {
161                 @Override
162                 public void onConfigurationChanged(Configuration configuration) {
163                     updateCacheSizeAndPreloadIfNeeded();
164                 }
165 
166                 @Override
167                 public void onLowMemory() {
168                 }
169             };
170             context.registerComponentCallbacks(componentCallbacks);
171             tracker.addCloseable(() -> context.unregisterComponentCallbacks(componentCallbacks));
172         }
173 
174         taskStackChangeListeners.registerTaskStackListener(this);
175         SafeCloseable iconChangeCloseable = iconProvider.registerIconChangeListener(
176                 this::onAppIconChanged, MAIN_EXECUTOR.getHandler());
177 
178         Runnable unlockCallback = () -> themeManagerLazy.get().addChangeListener(this);
179         lockedUserState.runOnUserUnlocked(unlockCallback);
180 
181         tracker.addCloseable(() -> {
182             taskStackChangeListeners.unregisterTaskStackListener(this);
183             iconChangeCloseable.close();
184             mIconCache.removeTaskVisualsChangeListener();
185             if (lockedUserState.isUserUnlocked()) {
186                 themeManagerLazy.get().removeChangeListener(this);
187             } else {
188                 lockedUserState.removeOnUserUnlockedRunnable(unlockCallback);
189             }
190         });
191     }
192 
getIconCache()193     public TaskIconCache getIconCache() {
194         return mIconCache;
195     }
196 
getThumbnailCache()197     public TaskThumbnailCache getThumbnailCache() {
198         return mThumbnailCache;
199     }
200 
201     /**
202      * Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks
203      * at the end of the list. Filters out desktop tasks that contain no non-minimized tasks.
204      *
205      * @param callback The callback to receive the task plan once its complete or null. This is
206      *                always called on the UI thread.
207      * @return the request id associated with this call.
208      */
209     @Override
getTasks(@ullable Consumer<List<GroupTask>> callback)210     public int getTasks(@Nullable Consumer<List<GroupTask>> callback) {
211         return mTaskList.getTasks(false /* loadKeysOnly */, callback,
212                 RecentsFilterState.getDesktopTaskFilter());
213     }
214 
215     /**
216      * Fetches the list of recent tasks, based on a filter
217      *
218      * @param callback The callback to receive the task plan once its complete or null. This is
219      *                always called on the UI thread.
220      * @param filter  Returns true if a GroupTask should be included into the list passed into
221      *                callback.
222      * @return the request id associated with this call.
223      */
getTasks(@ullable Consumer<List<GroupTask>> callback, Predicate<GroupTask> filter)224     public int getTasks(@Nullable Consumer<List<GroupTask>> callback, Predicate<GroupTask> filter) {
225         return mTaskList.getTasks(false /* loadKeysOnly */, callback, filter);
226     }
227 
228     /**
229      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
230      */
isTaskListValid(int changeId)231     public boolean isTaskListValid(int changeId) {
232         return mTaskList.isTaskListValid(changeId);
233     }
234 
235     /**
236      * @return Whether the task list is currently updating in the background
237      */
238     @VisibleForTesting
isLoadingTasksInBackground()239     public boolean isLoadingTasksInBackground() {
240         return mTaskList.isLoadingTasksInBackground();
241     }
242 
243     /**
244      * Checks if a task has been removed or not.
245      *
246      * @param callback Receives true if task is removed, false otherwise
247      * @param filter Returns true if GroupTask should be in the list of considerations
248      */
isTaskRemoved(int taskId, Consumer<Boolean> callback, Predicate<GroupTask> filter)249     public void isTaskRemoved(int taskId, Consumer<Boolean> callback, Predicate<GroupTask> filter) {
250         // Invalidate the existing list before checking to ensure this reflects the current state in
251         // the system
252         mTaskList.onRecentTasksChanged();
253         mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
254             for (GroupTask group : taskGroups) {
255                 if (group.containsTask(taskId)) {
256                     callback.accept(false);
257                     return;
258                 }
259             }
260             callback.accept(true);
261         }, filter);
262     }
263 
264     @Override
onTaskStackChangedBackground()265     public void onTaskStackChangedBackground() {
266         if (!mThumbnailCache.isPreloadingEnabled()) {
267             // Skip if we aren't preloading
268             return;
269         }
270 
271         int currentUserId = Process.myUserHandle().getIdentifier();
272         if (!checkCurrentOrManagedUserId(currentUserId, mContext)) {
273             // Skip if we are not the current user
274             return;
275         }
276 
277         // Keep the cache up to date with the latest thumbnails
278         ActivityManager.RunningTaskInfo runningTask =
279                 ActivityManagerWrapper.getInstance().getRunningTask();
280         int runningTaskId = runningTask != null ? runningTask.id : -1;
281         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
282             for (GroupTask group : taskGroups) {
283                 if (group.containsTask(runningTaskId)) {
284                     // Skip the running task, it's not going to have an up-to-date snapshot by the
285                     // time the user next enters overview
286                     continue;
287                 }
288                 group.getTasks().forEach(
289                         t -> mThumbnailCache.updateThumbnailInCache(t, /* lowResolution= */ true));
290             }
291         });
292     }
293 
294     @Override
onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)295     public boolean onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
296         mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
297 
298         for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
299             Task task = listener.onTaskThumbnailChanged(taskId, snapshot);
300             if (task != null) {
301                 task.thumbnail = snapshot;
302             }
303         }
304         return true;
305     }
306 
307     @Override
onTaskRemoved(int taskId)308     public void onTaskRemoved(int taskId) {
309         Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0);
310         mThumbnailCache.remove(stubKey);
311         mIconCache.onTaskRemoved(stubKey);
312     }
313 
onTrimMemory(int level)314     public void onTrimMemory(int level) {
315         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
316             mThumbnailCache.getHighResLoadingState().setVisible(false);
317         }
318         if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
319             // Clear everything once we reach a low-mem situation
320             mThumbnailCache.clear();
321             mIconCache.clearCache();
322         }
323     }
324 
onAppIconChanged(String packageName, UserHandle user)325     private void onAppIconChanged(String packageName, UserHandle user) {
326         mIconCache.invalidateCacheEntries(packageName, user);
327         for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
328             listener.onTaskIconChanged(packageName, user);
329         }
330     }
331 
332     @Override
onTaskIconChanged(int taskId)333     public void onTaskIconChanged(int taskId) {
334         for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
335             listener.onTaskIconChanged(taskId);
336         }
337     }
338 
339     @Override
onThemeChanged()340     public void onThemeChanged() {
341         mIconCache.clearCache();
342     }
343 
344     /**
345      * Adds a listener for visuals changes
346      */
347     @Override
addThumbnailChangeListener(TaskVisualsChangeListener listener)348     public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
349         mThumbnailChangeListeners.add(listener);
350     }
351 
352     /**
353      * Removes a previously added listener
354      */
355     @Override
removeThumbnailChangeListener(TaskVisualsChangeListener listener)356     public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
357         mThumbnailChangeListeners.remove(listener);
358     }
359 
dump(String prefix, PrintWriter writer)360     public void dump(String prefix, PrintWriter writer) {
361         writer.println(prefix + "RecentsModel:");
362         mTaskList.dump("  ", writer);
363     }
364 
365     /**
366      * Registers a listener for running tasks
367      * TODO(b/343292503): Should we remove RunningTasksListener entirely if it's not needed?
368      *  (Note that Desktop mode gets the running tasks by checking {@link DesktopTask#tasks}
369      */
registerRunningTasksListener(RunningTasksListener listener)370     public void registerRunningTasksListener(RunningTasksListener listener) {
371         mTaskList.registerRunningTasksListener(listener);
372     }
373 
374     /**
375      * Removes the previously registered running tasks listener
376      */
unregisterRunningTasksListener()377     public void unregisterRunningTasksListener() {
378         mTaskList.unregisterRunningTasksListener();
379     }
380 
381     /**
382      * Registers a listener for recent tasks
383      */
registerRecentTasksChangedListener(RecentTasksChangedListener listener)384     public void registerRecentTasksChangedListener(RecentTasksChangedListener listener) {
385         mTaskList.registerRecentTasksChangedListener(listener);
386     }
387 
388     /**
389      * Removes the previously registered running tasks listener
390      */
unregisterRecentTasksChangedListener()391     public void unregisterRecentTasksChangedListener() {
392         mTaskList.unregisterRecentTasksChangedListener();
393     }
394 
395     /**
396      * Gets the set of running tasks.
397      */
getRunningTasks()398     public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
399         return mTaskList.getRunningTasks();
400     }
401 
402     /**
403      * Preloads cache if enableGridOnlyOverview is true, preloading is enabled and
404      * highResLoadingState is enabled
405      */
preloadCacheIfNeeded()406     public void preloadCacheIfNeeded() {
407         if (!isCachePreloadingEnabled()) {
408             return;
409         }
410 
411         if (!mThumbnailCache.isPreloadingEnabled()) {
412             // Skip if we aren't preloading.
413             return;
414         }
415 
416         if (!mThumbnailCache.getHighResLoadingState().isEnabled()) {
417             // Skip if high-res loading state is disabled.
418             return;
419         }
420 
421         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
422             for (GroupTask group : taskGroups) {
423                 group.getTasks().forEach(
424                         t -> mThumbnailCache.updateThumbnailInCache(t, /* lowResolution= */ false));
425             }
426         });
427     }
428 
429     /**
430      * Updates cache size and preloads more tasks if cache size increases
431      */
updateCacheSizeAndPreloadIfNeeded()432     public void updateCacheSizeAndPreloadIfNeeded() {
433         if (!isCachePreloadingEnabled()) {
434             return;
435         }
436 
437         // If new size is larger than original size, preload more cache to fill the gap
438         if (mThumbnailCache.updateCacheSizeAndRemoveExcess()) {
439             preloadCacheIfNeeded();
440         }
441     }
442 
isCachePreloadingEnabled()443     private boolean isCachePreloadingEnabled() {
444         return enableGridOnlyOverview() || enableRefactorTaskThumbnail();
445     }
446 
447     /**
448      * Listener for receiving running tasks changes
449      */
450     public interface RunningTasksListener {
451         /**
452          * Called when there's a change to running tasks
453          */
onRunningTasksChanged()454         void onRunningTasksChanged();
455     }
456 
457     /**
458      * Listener for receiving recent tasks changes
459      */
460     public interface RecentTasksChangedListener {
461         /**
462          * Called when there's a change to recent tasks
463          */
onRecentTasksChanged()464         void onRecentTasksChanged();
465     }
466 }
467