| /* |
| * Copyright (C) 2014 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 com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; |
| |
| import android.annotation.TargetApi; |
| import android.app.ActivityManager; |
| import android.content.Context; |
| import android.os.Build; |
| import android.os.Process; |
| import android.util.SparseBooleanArray; |
| import com.android.launcher3.MainThreadExecutor; |
| import com.android.systemui.shared.recents.model.Task; |
| import com.android.systemui.shared.system.ActivityManagerWrapper; |
| import com.android.systemui.shared.system.KeyguardManagerCompat; |
| import com.android.systemui.shared.system.RecentTaskInfoCompat; |
| import com.android.systemui.shared.system.TaskDescriptionCompat; |
| import com.android.systemui.shared.system.TaskStackChangeListener; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.function.Consumer; |
| |
| /** |
| * Manages the recent task list from the system, caching it as necessary. |
| */ |
| @TargetApi(Build.VERSION_CODES.P) |
| public class RecentTasksList extends TaskStackChangeListener { |
| |
| private final KeyguardManagerCompat mKeyguardManager; |
| private final MainThreadExecutor mMainThreadExecutor; |
| |
| // The list change id, increments as the task list changes in the system |
| private int mChangeId; |
| // The last change id when the list was last loaded completely, must be <= the list change id |
| private int mLastLoadedId; |
| // The last change id was loaded with keysOnly = true |
| private boolean mLastLoadHadKeysOnly; |
| |
| ArrayList<Task> mTasks = new ArrayList<>(); |
| |
| public RecentTasksList(Context context) { |
| mMainThreadExecutor = new MainThreadExecutor(); |
| mKeyguardManager = new KeyguardManagerCompat(context); |
| mChangeId = 1; |
| ActivityManagerWrapper.getInstance().registerTaskStackListener(this); |
| } |
| |
| /** |
| * Fetches the task keys skipping any local cache. |
| */ |
| public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) { |
| // Kick off task loading in the background |
| BACKGROUND_EXECUTOR.execute(() -> { |
| ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */); |
| mMainThreadExecutor.execute(() -> callback.accept(tasks)); |
| }); |
| } |
| |
| /** |
| * Asynchronously fetches the list of recent tasks, reusing cached list if available. |
| * |
| * @param loadKeysOnly Whether to load other associated task data, or just the key |
| * @param callback The callback to receive the list of recent tasks |
| * @return The change id of the current task list |
| */ |
| public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) { |
| final int requestLoadId = mChangeId; |
| Runnable resultCallback = callback == null |
| ? () -> { } |
| : () -> callback.accept(copyOf(mTasks)); |
| |
| if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { |
| // The list is up to date, callback with the same list |
| mMainThreadExecutor.execute(resultCallback); |
| return requestLoadId; |
| } |
| |
| // Kick off task loading in the background |
| BACKGROUND_EXECUTOR.execute(() -> { |
| ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly); |
| |
| mMainThreadExecutor.execute(() -> { |
| mTasks = tasks; |
| mLastLoadedId = requestLoadId; |
| mLastLoadHadKeysOnly = loadKeysOnly; |
| resultCallback.run(); |
| }); |
| }); |
| |
| return requestLoadId; |
| } |
| |
| /** |
| * @return Whether the provided {@param changeId} is the latest recent tasks list id. |
| */ |
| public synchronized boolean isTaskListValid(int changeId) { |
| return mChangeId == changeId; |
| } |
| |
| @Override |
| public synchronized void onTaskStackChanged() { |
| mChangeId++; |
| } |
| |
| @Override |
| public void onTaskRemoved(int taskId) { |
| for (int i = mTasks.size() - 1; i >= 0; i--) { |
| if (mTasks.get(i).key.id == taskId) { |
| mTasks.remove(i); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void onActivityPinned(String packageName, int userId, int taskId, |
| int stackId) { |
| mChangeId++; |
| } |
| |
| @Override |
| public synchronized void onActivityUnpinned() { |
| mChangeId++; |
| } |
| |
| /** |
| * Loads and creates a list of all the recent tasks. |
| */ |
| private ArrayList<Task> loadTasksInBackground(int numTasks, |
| boolean loadKeysOnly) { |
| int currentUserId = Process.myUserHandle().getIdentifier(); |
| ArrayList<Task> allTasks = new ArrayList<>(); |
| List<ActivityManager.RecentTaskInfo> rawTasks = |
| ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId); |
| // The raw tasks are given in most-recent to least-recent order, we need to reverse it |
| Collections.reverse(rawTasks); |
| |
| SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() { |
| @Override |
| public boolean get(int key) { |
| if (indexOfKey(key) < 0) { |
| // Fill the cached locked state as we fetch |
| put(key, mKeyguardManager.isDeviceLocked(key)); |
| } |
| return super.get(key); |
| } |
| }; |
| |
| int taskCount = rawTasks.size(); |
| for (int i = 0; i < taskCount; i++) { |
| ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i); |
| RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask); |
| Task.TaskKey taskKey = new Task.TaskKey(rawTask); |
| Task task; |
| if (!loadKeysOnly) { |
| ActivityManager.TaskDescription rawTd = t.getTaskDescription(); |
| TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd); |
| boolean isLocked = tmpLockedUsers.get(t.getUserId()); |
| task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(), |
| t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity()); |
| } else { |
| task = new Task(taskKey); |
| } |
| allTasks.add(task); |
| } |
| |
| return allTasks; |
| } |
| |
| private ArrayList<Task> copyOf(ArrayList<Task> tasks) { |
| ArrayList<Task> newTasks = new ArrayList<>(); |
| for (int i = 0; i < tasks.size(); i++) { |
| Task t = tasks.get(i); |
| newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable, |
| t.isLocked, t.taskDescription, t.topActivity)); |
| } |
| return newTasks; |
| } |
| } |