| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.quickstep; |
| |
| import static android.os.Process.THREAD_PRIORITY_BACKGROUND; |
| |
| import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; |
| import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; |
| |
| import android.annotation.TargetApi; |
| import android.app.ActivityManager; |
| import android.app.KeyguardManager; |
| import android.content.ComponentCallbacks2; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Build; |
| import android.os.Process; |
| import android.os.UserHandle; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.launcher3.icons.IconProvider; |
| import com.android.launcher3.icons.IconProvider.IconChangeListener; |
| import com.android.launcher3.util.Executors.SimpleThreadFactory; |
| import com.android.launcher3.util.MainThreadInitializedObject; |
| import com.android.quickstep.util.GroupTask; |
| import com.android.quickstep.util.TaskVisualsChangeListener; |
| import com.android.systemui.shared.recents.model.Task; |
| import com.android.systemui.shared.recents.model.ThumbnailData; |
| import com.android.systemui.shared.system.ActivityManagerWrapper; |
| import com.android.systemui.shared.system.TaskStackChangeListener; |
| import com.android.systemui.shared.system.TaskStackChangeListeners; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| import java.util.function.Consumer; |
| |
| /** |
| * Singleton class to load and manage recents model. |
| */ |
| @TargetApi(Build.VERSION_CODES.O) |
| public class RecentsModel implements IconChangeListener, TaskStackChangeListener, |
| TaskVisualsChangeListener { |
| |
| // We do not need any synchronization for this variable as its only written on UI thread. |
| public static final MainThreadInitializedObject<RecentsModel> INSTANCE = |
| new MainThreadInitializedObject<>(RecentsModel::new); |
| |
| private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor( |
| new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND)); |
| |
| private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>(); |
| private final Context mContext; |
| |
| private final RecentTasksList mTaskList; |
| private final TaskIconCache mIconCache; |
| private final TaskThumbnailCache mThumbnailCache; |
| |
| private RecentsModel(Context context) { |
| mContext = context; |
| mTaskList = new RecentTasksList(MAIN_EXECUTOR, |
| context.getSystemService(KeyguardManager.class), |
| SystemUiProxy.INSTANCE.get(context)); |
| |
| IconProvider iconProvider = new IconProvider(context); |
| mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider); |
| mIconCache.registerTaskVisualsChangeListener(this); |
| mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR); |
| |
| TaskStackChangeListeners.getInstance().registerTaskStackListener(this); |
| iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler()); |
| } |
| |
| public TaskIconCache getIconCache() { |
| return mIconCache; |
| } |
| |
| public TaskThumbnailCache getThumbnailCache() { |
| return mThumbnailCache; |
| } |
| |
| /** |
| * Fetches the list of recent tasks. |
| * |
| * @param callback The callback to receive the task plan once its complete or null. This is |
| * always called on the UI thread. |
| * @return the request id associated with this call. |
| */ |
| public int getTasks(Consumer<ArrayList<GroupTask>> callback) { |
| return mTaskList.getTasks(false /* loadKeysOnly */, callback); |
| } |
| |
| /** |
| * @return Whether the provided {@param changeId} is the latest recent tasks list id. |
| */ |
| public boolean isTaskListValid(int changeId) { |
| return mTaskList.isTaskListValid(changeId); |
| } |
| |
| /** |
| * @return Whether the task list is currently updating in the background |
| */ |
| @VisibleForTesting |
| public boolean isLoadingTasksInBackground() { |
| return mTaskList.isLoadingTasksInBackground(); |
| } |
| |
| /** |
| * Checks if a task has been removed or not. |
| * |
| * @param callback Receives true if task is removed, false otherwise |
| */ |
| public void isTaskRemoved(int taskId, Consumer<Boolean> callback) { |
| mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> { |
| for (GroupTask group : taskGroups) { |
| if (group.containsTask(taskId)) { |
| callback.accept(false); |
| return; |
| } |
| } |
| callback.accept(true); |
| }); |
| } |
| |
| @Override |
| public void onTaskStackChangedBackground() { |
| if (!mThumbnailCache.isPreloadingEnabled()) { |
| // Skip if we aren't preloading |
| return; |
| } |
| |
| int currentUserId = Process.myUserHandle().getIdentifier(); |
| if (!checkCurrentOrManagedUserId(currentUserId, mContext)) { |
| // Skip if we are not the current user |
| return; |
| } |
| |
| // Keep the cache up to date with the latest thumbnails |
| ActivityManager.RunningTaskInfo runningTask = |
| ActivityManagerWrapper.getInstance().getRunningTask(); |
| int runningTaskId = runningTask != null ? runningTask.id : -1; |
| mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> { |
| for (GroupTask group : taskGroups) { |
| if (group.containsTask(runningTaskId)) { |
| // Skip the running task, it's not going to have an up-to-date snapshot by the |
| // time the user next enters overview |
| continue; |
| } |
| mThumbnailCache.updateThumbnailInCache(group.task1); |
| mThumbnailCache.updateThumbnailInCache(group.task2); |
| } |
| }); |
| } |
| |
| @Override |
| public boolean onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { |
| mThumbnailCache.updateTaskSnapShot(taskId, snapshot); |
| |
| for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { |
| Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot); |
| if (task != null) { |
| task.thumbnail = snapshot; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public void onTaskRemoved(int taskId) { |
| Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0); |
| mThumbnailCache.remove(stubKey); |
| mIconCache.onTaskRemoved(stubKey); |
| } |
| |
| public void onTrimMemory(int level) { |
| if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { |
| mThumbnailCache.getHighResLoadingState().setVisible(false); |
| } |
| if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { |
| // Clear everything once we reach a low-mem situation |
| mThumbnailCache.clear(); |
| mIconCache.clearCache(); |
| } |
| } |
| |
| @Override |
| public void onAppIconChanged(String packageName, UserHandle user) { |
| mIconCache.invalidateCacheEntries(packageName, user); |
| for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { |
| mThumbnailChangeListeners.get(i).onTaskIconChanged(packageName, user); |
| } |
| } |
| |
| @Override |
| public void onTaskIconChanged(int taskId) { |
| for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) { |
| listener.onTaskIconChanged(taskId); |
| } |
| } |
| |
| @Override |
| public void onSystemIconStateChanged(String iconState) { |
| mIconCache.clearCache(); |
| } |
| |
| /** |
| * Adds a listener for visuals changes |
| */ |
| public void addThumbnailChangeListener(TaskVisualsChangeListener listener) { |
| mThumbnailChangeListeners.add(listener); |
| } |
| |
| /** |
| * Removes a previously added listener |
| */ |
| public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) { |
| mThumbnailChangeListeners.remove(listener); |
| } |
| |
| public void dump(String prefix, PrintWriter writer) { |
| writer.println(prefix + "RecentsModel:"); |
| mTaskList.dump(" ", writer); |
| } |
| |
| /** |
| * Registers a listener for running tasks |
| */ |
| public void registerRunningTasksListener(RunningTasksListener listener) { |
| mTaskList.registerRunningTasksListener(listener); |
| } |
| |
| /** |
| * Removes the previously registered running tasks listener |
| */ |
| public void unregisterRunningTasksListener() { |
| mTaskList.unregisterRunningTasksListener(); |
| } |
| |
| /** |
| * Gets the set of running tasks. |
| */ |
| public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() { |
| return mTaskList.getRunningTasks(); |
| } |
| |
| /** |
| * Listener for receiving running tasks changes |
| */ |
| public interface RunningTasksListener { |
| /** |
| * Called when there's a change to running tasks |
| */ |
| void onRunningTasksChanged(); |
| } |
| } |