1 /*
2  * Copyright (C) 2014 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 
17 package com.android.quickstep;
18 
19 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
20 
21 import android.annotation.TargetApi;
22 import android.app.ActivityManager;
23 import android.os.Build;
24 import android.os.Process;
25 import android.util.SparseBooleanArray;
26 
27 import androidx.annotation.VisibleForTesting;
28 
29 import com.android.launcher3.util.LooperExecutor;
30 import com.android.systemui.shared.recents.model.Task;
31 import com.android.systemui.shared.system.ActivityManagerWrapper;
32 import com.android.systemui.shared.system.KeyguardManagerCompat;
33 import com.android.systemui.shared.system.TaskStackChangeListener;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.function.Consumer;
39 
40 /**
41  * Manages the recent task list from the system, caching it as necessary.
42  */
43 @TargetApi(Build.VERSION_CODES.P)
44 public class RecentTasksList extends TaskStackChangeListener {
45 
46     private final KeyguardManagerCompat mKeyguardManager;
47     private final LooperExecutor mMainThreadExecutor;
48     private final ActivityManagerWrapper mActivityManagerWrapper;
49 
50     // The list change id, increments as the task list changes in the system
51     private int mChangeId;
52     // The last change id when the list was last loaded completely, must be <= the list change id
53     private int mLastLoadedId;
54     // The last change id was loaded with keysOnly  = true
55     private boolean mLastLoadHadKeysOnly;
56 
57     ArrayList<Task> mTasks = new ArrayList<>();
58 
RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper)59     public RecentTasksList(LooperExecutor mainThreadExecutor,
60             KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
61         mMainThreadExecutor = mainThreadExecutor;
62         mKeyguardManager = keyguardManager;
63         mChangeId = 1;
64         mActivityManagerWrapper = activityManagerWrapper;
65         mActivityManagerWrapper.registerTaskStackListener(this);
66     }
67 
68     /**
69      * Fetches the task keys skipping any local cache.
70      */
getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback)71     public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
72         // Kick off task loading in the background
73         UI_HELPER_EXECUTOR.execute(() -> {
74             ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
75             mMainThreadExecutor.execute(() -> callback.accept(tasks));
76         });
77     }
78 
79     /**
80      * Asynchronously fetches the list of recent tasks, reusing cached list if available.
81      *
82      * @param loadKeysOnly Whether to load other associated task data, or just the key
83      * @param callback The callback to receive the list of recent tasks
84      * @return The change id of the current task list
85      */
getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback)86     public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
87         final int requestLoadId = mChangeId;
88         Runnable resultCallback = callback == null
89                 ? () -> { }
90                 : () -> callback.accept(copyOf(mTasks));
91 
92         if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
93             // The list is up to date, send the callback on the next frame,
94             // so that requestID can be returned first.
95             mMainThreadExecutor.post(resultCallback);
96             return requestLoadId;
97         }
98 
99         // Kick off task loading in the background
100         UI_HELPER_EXECUTOR.execute(() -> {
101             ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
102 
103             mMainThreadExecutor.execute(() -> {
104                 mTasks = tasks;
105                 mLastLoadedId = requestLoadId;
106                 mLastLoadHadKeysOnly = loadKeysOnly;
107                 resultCallback.run();
108             });
109         });
110 
111         return requestLoadId;
112     }
113 
114     /**
115      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
116      */
isTaskListValid(int changeId)117     public synchronized boolean isTaskListValid(int changeId) {
118         return mChangeId == changeId;
119     }
120 
121     @Override
onTaskStackChanged()122     public synchronized void onTaskStackChanged() {
123         mChangeId++;
124     }
125 
126     @Override
onTaskRemoved(int taskId)127     public void onTaskRemoved(int taskId) {
128         mTasks = loadTasksInBackground(Integer.MAX_VALUE, false);
129     }
130 
131     @Override
onActivityPinned(String packageName, int userId, int taskId, int stackId)132     public synchronized void onActivityPinned(String packageName, int userId, int taskId,
133             int stackId) {
134         mChangeId++;
135     }
136 
137     @Override
onActivityUnpinned()138     public synchronized void onActivityUnpinned() {
139         mChangeId++;
140     }
141 
142     /**
143      * Loads and creates a list of all the recent tasks.
144      */
145     @VisibleForTesting
loadTasksInBackground(int numTasks, boolean loadKeysOnly)146     ArrayList<Task> loadTasksInBackground(int numTasks,
147             boolean loadKeysOnly) {
148         int currentUserId = Process.myUserHandle().getIdentifier();
149         ArrayList<Task> allTasks = new ArrayList<>();
150         List<ActivityManager.RecentTaskInfo> rawTasks =
151                 mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
152         // The raw tasks are given in most-recent to least-recent order, we need to reverse it
153         Collections.reverse(rawTasks);
154 
155         SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
156             @Override
157             public boolean get(int key) {
158                 if (indexOfKey(key) < 0) {
159                     // Fill the cached locked state as we fetch
160                     put(key, mKeyguardManager.isDeviceLocked(key));
161                 }
162                 return super.get(key);
163             }
164         };
165 
166         int taskCount = rawTasks.size();
167         for (int i = 0; i < taskCount; i++) {
168             ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
169             Task.TaskKey taskKey = new Task.TaskKey(rawTask);
170             Task task;
171             if (!loadKeysOnly) {
172                 boolean isLocked = tmpLockedUsers.get(taskKey.userId);
173                 task = Task.from(taskKey, rawTask, isLocked);
174             } else {
175                 task = new Task(taskKey);
176             }
177             allTasks.add(task);
178         }
179 
180         return allTasks;
181     }
182 
copyOf(ArrayList<Task> tasks)183     private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
184         ArrayList<Task> newTasks = new ArrayList<>();
185         for (int i = 0; i < tasks.size(); i++) {
186             Task t = tasks.get(i);
187             newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
188                     t.isLocked, t.taskDescription, t.topActivity));
189         }
190         return newTasks;
191     }
192 }