Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f097e628 authored by Winson Chung's avatar Winson Chung
Browse files

Call into shell for recent tasks

- No change in behavior from today until groups are actually returned
  from the shell

Bug: 202740477
Test: atest RecentTasksListTest
Change-Id: I4ac7b472ce2e0a3b2574dc6d8f4c1761a0ad993a
Merged-In: I4ac7b472ce2e0a3b2574dc6d8f4c1761a0ad993a
parent c29f5441
Loading
Loading
Loading
Loading
+50 −65
Original line number Diff line number Diff line
@@ -22,35 +22,33 @@ import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import android.os.RemoteException;
import android.util.SparseBooleanArray;

import androidx.annotation.VisibleForTesting;

import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.LooperExecutor;
import com.android.systemui.shared.recents.model.GroupTask;
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.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;

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.R)
public class RecentTasksList extends TaskStackChangeListener {
public class RecentTasksList {

    private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);

    private final KeyguardManagerCompat mKeyguardManager;
    private final LooperExecutor mMainThreadExecutor;
    private final ActivityManagerWrapper mActivityManagerWrapper;
    private final SystemUiProxy mSysUiProxy;

    // The list change id, increments as the task list changes in the system
    private int mChangeId;
@@ -62,12 +60,17 @@ public class RecentTasksList extends TaskStackChangeListener {
    private TaskLoadResult mResultsUi = INVALID_RESULT;

    public RecentTasksList(LooperExecutor mainThreadExecutor,
            KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
            KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) {
        mMainThreadExecutor = mainThreadExecutor;
        mKeyguardManager = keyguardManager;
        mChangeId = 1;
        mActivityManagerWrapper = activityManagerWrapper;
        TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
        mSysUiProxy = sysUiProxy;
        sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
            @Override
            public void onRecentTasksChanged() throws RemoteException {
                mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
            }
        });
    }

    @VisibleForTesting
@@ -78,10 +81,11 @@ public class RecentTasksList extends TaskStackChangeListener {
    /**
     * Fetches the task keys skipping any local cache.
     */
    public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
    public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) {
        // Kick off task loading in the background
        UI_HELPER_EXECUTOR.execute(() -> {
            ArrayList<Task> tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */);
            ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1,
                    true /* loadKeysOnly */);
            mMainThreadExecutor.execute(() -> callback.accept(tasks));
        });
    }
@@ -93,14 +97,15 @@ public class RecentTasksList extends TaskStackChangeListener {
     * @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) {
    public synchronized int getTasks(boolean loadKeysOnly,
            Consumer<ArrayList<GroupTask>> callback) {
        final int requestLoadId = mChangeId;
        if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
            // The list is up to date, send the callback on the next frame,
            // so that requestID can be returned first.
            if (callback != null) {
                // Copy synchronously as the changeId might change by next frame
                ArrayList<Task> result = copyOf(mResultsUi);
                ArrayList<GroupTask> result = copyOf(mResultsUi);
                mMainThreadExecutor.post(() -> {
                    callback.accept(result);
                });
@@ -120,7 +125,7 @@ public class RecentTasksList extends TaskStackChangeListener {
                mLoadingTasksInBackground = false;
                mResultsUi = loadResult;
                if (callback != null) {
                    ArrayList<Task> result = copyOf(mResultsUi);
                    ArrayList<GroupTask> result = copyOf(mResultsUi);
                    callback.accept(result);
                }
            });
@@ -136,35 +141,7 @@ public class RecentTasksList extends TaskStackChangeListener {
        return mChangeId == changeId;
    }

    @Override
    public void onTaskStackChanged() {
        invalidateLoadedTasks();
    }

    @Override
    public void onRecentTaskListUpdated() {
        // In some cases immediately after booting, the tasks in the system recent task list may be
        // loaded, but not in the active task hierarchy in the system.  These tasks are displayed in 
        // overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved()
        // callback (those are for changes to the active tasks), but the task list is still updated,
        // so we should also invalidate the change id to ensure we load a new list instead of 
        // reusing a stale list.
        invalidateLoadedTasks();
    }

    @Override
    public void onTaskRemoved(int taskId) {
        invalidateLoadedTasks();
    }


    @Override
    public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
        invalidateLoadedTasks();
    }

    @Override
    public synchronized void onActivityUnpinned() {
    public void onRecentTasksChanged() {
        invalidateLoadedTasks();
    }

@@ -180,8 +157,8 @@ public class RecentTasksList extends TaskStackChangeListener {
    @VisibleForTesting
    TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
        int currentUserId = Process.myUserHandle().getIdentifier();
        List<ActivityManager.RecentTaskInfo> rawTasks =
                mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
        ArrayList<GroupedRecentTaskInfo> rawTasks =
                mSysUiProxy.getRecentTasks(numTasks, currentUserId);
        // The raw tasks are given in most-recent to least-recent order, we need to reverse it
        Collections.reverse(rawTasks);

@@ -197,45 +174,53 @@ public class RecentTasksList extends TaskStackChangeListener {
        };

        TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
        for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
            Task.TaskKey taskKey = new Task.TaskKey(rawTask);
            Task task;
            if (!loadKeysOnly) {
                boolean isLocked = tmpLockedUsers.get(taskKey.userId);
                task = Task.from(taskKey, rawTask, isLocked);
            } else {
                task = new Task(taskKey);
            }
            task.setLastSnapshotData(rawTask);
            allTasks.add(task);
        for (GroupedRecentTaskInfo rawTask : rawTasks) {
            ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1;
            ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2;
            Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
            Task task1 = loadKeysOnly
                    ? new Task(task1Key)
                    : Task.from(task1Key, taskInfo1,
                            tmpLockedUsers.get(task1Key.userId) /* isLocked */);
            task1.setLastSnapshotData(taskInfo1);
            Task task2 = null;
            if (taskInfo2 != null) {
                Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
                task2 = loadKeysOnly
                        ? new Task(task2Key)
                        : Task.from(task2Key, taskInfo2,
                                tmpLockedUsers.get(task2Key.userId) /* isLocked */);
                task2.setLastSnapshotData(taskInfo2);
            }
            allTasks.add(new GroupTask(task1, task2));
        }

        return allTasks;
    }

    private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
        ArrayList<Task> newTasks = new ArrayList<>();
    private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
        ArrayList<GroupTask> newTasks = new ArrayList<>();
        for (int i = 0; i < tasks.size(); i++) {
            newTasks.add(new Task(tasks.get(i)));
            newTasks.add(new GroupTask(tasks.get(i)));
        }
        return newTasks;
    }

    private static class TaskLoadResult extends ArrayList<Task> {
    private static class TaskLoadResult extends ArrayList<GroupTask> {

        final int mId;
        final int mRequestId;

        // If the result was loaded with keysOnly  = true
        final boolean mKeysOnly;

        TaskLoadResult(int id, boolean keysOnly, int size) {
        TaskLoadResult(int requestId, boolean keysOnly, int size) {
            super(size);
            mId = id;
            mRequestId = requestId;
            mKeysOnly = keysOnly;
        }

        boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
            return mId == requestId && (!mKeysOnly || loadKeysOnly);
            return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
        }
    }
}
 No newline at end of file
+11 −9
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ 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.systemui.shared.recents.model.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -70,7 +71,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
    private RecentsModel(Context context) {
        mContext = context;
        mTaskList = new RecentTasksList(MAIN_EXECUTOR,
                new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
                new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context));

        IconProvider iconProvider = new IconProvider(context);
        mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
@@ -95,7 +96,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
     *                always called on the UI thread.
     * @return the request id associated with this call.
     */
    public int getTasks(Consumer<ArrayList<Task>> callback) {
    public int getTasks(Consumer<ArrayList<GroupTask>> callback) {
        return mTaskList.getTasks(false /* loadKeysOnly */, callback);
    }

@@ -120,9 +121,9 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
     * @param callback Receives true if task is removed, false otherwise
     */
    public void isTaskRemoved(int taskId, Consumer<Boolean> callback) {
        mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
            for (Task task : tasks) {
                if (task.key.id == taskId) {
        mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
            for (GroupTask group : taskGroups) {
                if (group.containsTask(taskId)) {
                    callback.accept(false);
                    return;
                }
@@ -148,14 +149,15 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
        ActivityManager.RunningTaskInfo runningTask =
                ActivityManagerWrapper.getInstance().getRunningTask();
        int runningTaskId = runningTask != null ? runningTask.id : -1;
        mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
            for (Task task : tasks) {
                if (task.key.id == runningTaskId) {
        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(task);
                mThumbnailCache.updateThumbnailInCache(group.task1);
                mThumbnailCache.updateThumbnailInCache(group.task2);
            }
        });
    }
+54 −3
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package com.android.quickstep;

import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;

import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;

import android.app.PendingIntent;
@@ -47,6 +49,9 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
@@ -54,6 +59,7 @@ import com.android.wm.shell.startingsurface.IStartingWindowListener;
import com.android.wm.shell.transition.IShellTransitions;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * Holds the reference to SystemUI.
@@ -72,6 +78,7 @@ public class SystemUiProxy implements ISystemUiProxy,
    private IOneHanded mOneHanded;
    private IShellTransitions mShellTransitions;
    private IStartingWindow mStartingWindow;
    private IRecentTasks mRecentTasks;
    private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
        MAIN_EXECUTOR.execute(() -> clearProxy());
    };
@@ -82,6 +89,7 @@ public class SystemUiProxy implements ISystemUiProxy,
    private ISplitScreenListener mPendingSplitScreenListener;
    private IStartingWindowListener mPendingStartingWindowListener;
    private ISmartspaceCallback mPendingSmartspaceCallback;
    private IRecentTasksListener mPendingRecentTasksListener;
    private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>();

    // Used to dedupe calls to SystemUI
@@ -135,7 +143,7 @@ public class SystemUiProxy implements ISystemUiProxy,

    public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
            IOneHanded oneHanded, IShellTransitions shellTransitions,
            IStartingWindow startingWindow,
            IStartingWindow startingWindow, IRecentTasks recentTasks,
            ISmartspaceTransitionController smartSpaceTransitionController) {
        unlinkToDeath();
        mSystemUiProxy = proxy;
@@ -145,6 +153,7 @@ public class SystemUiProxy implements ISystemUiProxy,
        mShellTransitions = shellTransitions;
        mStartingWindow = startingWindow;
        mSmartspaceTransitionController = smartSpaceTransitionController;
        mRecentTasks = recentTasks;
        linkToDeath();
        // re-attach the listeners once missing due to setProxy has not been initialized yet.
        if (mPendingPipAnimationListener != null && mPip != null) {
@@ -167,6 +176,10 @@ public class SystemUiProxy implements ISystemUiProxy,
            registerRemoteTransition(mPendingRemoteTransitions.get(i));
        }
        mPendingRemoteTransitions.clear();
        if (mPendingRecentTasksListener != null && mRecentTasks != null) {
            registerRecentTasksListener(mPendingRecentTasksListener);
            mPendingRecentTasksListener = null;
        }

        if (mPendingSetNavButtonAlpha != null) {
            mPendingSetNavButtonAlpha.run();
@@ -175,7 +188,7 @@ public class SystemUiProxy implements ISystemUiProxy,
    }

    public void clearProxy() {
        setProxy(null, null, null, null, null, null, null);
        setProxy(null, null, null, null, null, null, null, null);
    }

    // TODO(141886704): Find a way to remove this
@@ -745,7 +758,6 @@ public class SystemUiProxy implements ISystemUiProxy,
        }
    }


    //
    // SmartSpace transitions
    //
@@ -761,4 +773,43 @@ public class SystemUiProxy implements ISystemUiProxy,
            mPendingSmartspaceCallback = callback;
        }
    }

    //
    // Recents
    //

    public void registerRecentTasksListener(IRecentTasksListener listener) {
        if (mRecentTasks != null) {
            try {
                mRecentTasks.registerRecentTasksListener(listener);
            } catch (RemoteException e) {
                Log.w(TAG, "Failed call registerRecentTasksListener", e);
            }
        } else {
            mPendingRecentTasksListener = listener;
        }
    }

    public void unregisterRecentTasksListener(IRecentTasksListener listener) {
        if (mRecentTasks != null) {
            try {
                mRecentTasks.unregisterRecentTasksListener(listener);
            } catch (RemoteException e) {
                Log.w(TAG, "Failed call unregisterRecentTasksListener");
            }
        }
        mPendingRecentTasksListener = null;
    }

    public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
        if (mRecentTasks != null) {
            try {
                return new ArrayList<>(Arrays.asList(mRecentTasks.getRecentTasks(numTasks,
                        RECENT_IGNORE_UNAVAILABLE, userId)));
            } catch (RemoteException e) {
                Log.w(TAG, "Failed call getRecentTasks", e);
            }
        }
        return new ArrayList<>();
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -104,6 +104,9 @@ public class TaskThumbnailCache {
     * Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
     */
    public void updateThumbnailInCache(Task task) {
        if (task == null) {
            return;
        }
        Preconditions.assertUIThread();
        // Fetch the thumbnail for this task and put it in the cache
        if (task.thumbnail == null) {
+5 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -112,6 +113,7 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.transition.IShellTransitions;
@@ -170,9 +172,11 @@ public class TouchInteractionService extends Service
            ISmartspaceTransitionController smartspaceTransitionController =
                    ISmartspaceTransitionController.Stub.asInterface(
                            bundle.getBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER));
            IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_RECENT_TASKS));
            MAIN_EXECUTOR.execute(() -> {
                SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
                        splitscreen, onehanded, shellTransitions, startingWindow,
                        splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
                        smartspaceTransitionController);
                TouchInteractionService.this.initInputMonitor();
                preloadOverview(true /* fromInit */);
Loading