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

Commit e683c4c5 authored by Pat Manning's avatar Pat Manning
Browse files

Navigate grid overview via arrows and tab properly.

Fix: 311646336
Fix: 281021558
Test: TaskGridNavHelperTest
Flag: N/A
Change-Id: Iad5a2eec22f8fa4519a6a1f7bcde097e00d6b142
parent 2e54a166
Loading
Loading
Loading
Loading
+136 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.util;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import androidx.annotation.IntDef;

import com.android.launcher3.util.IntArray;

import java.lang.annotation.Retention;

/**
 * Helper class for navigating RecentsView grid tasks via arrow keys and tab.
 */
public class TaskGridNavHelper {
    public static final int CLEAR_ALL_PLACEHOLDER_ID = -1;
    public static final int INVALID_FOCUSED_TASK_ID = -1;

    public static final int DIRECTION_UP = 0;
    public static final int DIRECTION_DOWN = 1;
    public static final int DIRECTION_LEFT = 2;
    public static final int DIRECTION_RIGHT = 3;
    public static final int DIRECTION_TAB = 4;

    @Retention(SOURCE)
    @IntDef({DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TAB})
    public @interface TASK_NAV_DIRECTION {}

    private final IntArray mOriginalTopRowIds;
    private IntArray mTopRowIds;
    private IntArray mBottomRowIds;
    private final int mFocusedTaskId;

    public TaskGridNavHelper(IntArray topIds, IntArray bottomIds, int focusedTaskId) {
        mFocusedTaskId = focusedTaskId;
        mOriginalTopRowIds = topIds.clone();
        generateTaskViewIdGrid(topIds, bottomIds);
    }

    private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray) {
        boolean hasFocusedTask = mFocusedTaskId != INVALID_FOCUSED_TASK_ID;
        int maxSize =
                Math.max(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0);
        int minSize =
                Math.min(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0);

        // Add the focused task to the beginning of both arrays if it exists.
        if (hasFocusedTask) {
            topRowIdArray.add(0, mFocusedTaskId);
            bottomRowIdArray.add(0, mFocusedTaskId);
        }

        // Fill in the shorter array with the ids from the longer one.
        for (int i = minSize; i < maxSize; i++) {
            if (i >= topRowIdArray.size()) {
                topRowIdArray.add(bottomRowIdArray.get(i));
            } else {
                bottomRowIdArray.add(topRowIdArray.get(i));
            }
        }

        // Add the clear all button to the end of both arrays
        topRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID);
        bottomRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID);

        mTopRowIds = topRowIdArray;
        mBottomRowIds = bottomRowIdArray;
    }

    /**
     * Returns the id of the next page in the grid or -1 for the clear all button.
     */
    public int getNextGridPage(int currentPageTaskViewId, int delta,
            @TASK_NAV_DIRECTION int direction, boolean cycle) {
        boolean inTop = mTopRowIds.contains(currentPageTaskViewId);
        int index = inTop ? mTopRowIds.indexOf(currentPageTaskViewId)
                : mBottomRowIds.indexOf(currentPageTaskViewId);
        int maxSize = Math.max(mTopRowIds.size(), mBottomRowIds.size());
        int nextIndex = index + delta;

        switch (direction) {
            case DIRECTION_UP:
            case DIRECTION_DOWN: {
                return inTop ? mBottomRowIds.get(index) : mTopRowIds.get(index);
            }
            case DIRECTION_LEFT: {
                int boundedIndex = cycle ? nextIndex % maxSize : Math.min(nextIndex, maxSize - 1);
                return inTop ? mTopRowIds.get(boundedIndex)
                        : mBottomRowIds.get(boundedIndex);
            }
            case DIRECTION_RIGHT: {
                int boundedIndex =
                        cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max(
                                nextIndex, 0);
                boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId);
                return inOriginalTop ? mTopRowIds.get(boundedIndex)
                        : mBottomRowIds.get(boundedIndex);
            }
            case DIRECTION_TAB: {
                int boundedIndex =
                        cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min(
                                nextIndex, maxSize - 1);
                if (delta >= 0) {
                    return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index)
                            ? mBottomRowIds.get(index)
                            : mTopRowIds.get(boundedIndex);
                } else {
                    if (mTopRowIds.contains(currentPageTaskViewId)) {
                        return mBottomRowIds.get(boundedIndex);
                    } else {
                        // Go up to top if there is task above
                        return mTopRowIds.get(index) != mBottomRowIds.get(index)
                                ? mTopRowIds.get(index)
                                : mBottomRowIds.get(boundedIndex);
                    }
                }
            }
            default:
                return currentPageTaskViewId;
        }
    }
}
+52 −7
Original line number Diff line number Diff line
@@ -55,6 +55,11 @@ import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VAL
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN;
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT;
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_RIGHT;
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_TAB;
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP;
import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
@@ -196,6 +201,7 @@ import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskGridNavHelper;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.quickstep.util.TransformParams;
@@ -4054,12 +4060,19 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        return anim;
    }

    private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) {
    private boolean snapToPageRelative(int delta, boolean cycle,
            @TaskGridNavHelper.TASK_NAV_DIRECTION int direction) {
        // Ignore grid page snap events while scroll animations are running, otherwise the next
        // page gets set before the animation finishes and can cause jumps.
        if (!mScroller.isFinished()) {
            return true;
        }
        int pageCount = getPageCount();
        if (pageCount == 0) {
            return false;
        }
        final int newPageUnbound = getNextPage() + delta;
        if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) {
        final int newPageUnbound = getNextPageInternal(delta, direction, cycle);
        if (!cycle && (newPageUnbound < 0 || newPageUnbound > pageCount)) {
            return false;
        }
        snapToPage((newPageUnbound + pageCount) % pageCount);
@@ -4067,6 +4080,34 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        return true;
    }

    private int getNextPageInternal(int delta, @TaskGridNavHelper.TASK_NAV_DIRECTION int direction,
            boolean cycle) {
        if (!showAsGrid()) {
            return getNextPage() + delta;
        }

        // Init task grid nav helper with top/bottom id arrays.
        TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(),
                getBottomRowIdArray(), mFocusedTaskViewId);

        // Get current page's task view ID.
        TaskView currentPageTaskView = getCurrentPageTaskView();
        int currentPageTaskViewId;
        if (currentPageTaskView != null) {
            currentPageTaskViewId = currentPageTaskView.getTaskViewId();
        } else if (mCurrentPage == indexOfChild(mClearAllButton)) {
            currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID;
        } else {
            return INVALID_PAGE;
        }

        int nextGridPage =
                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
        return nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID
                ? indexOfChild(mClearAllButton)
                : indexOfChild(getTaskViewFromTaskViewId(nextGridPage));
    }

    private void runDismissAnimation(PendingAnimation pendingAnim) {
        AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
        controller.dispatchOnStart();
@@ -4108,12 +4149,16 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_TAB:
                    return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1,
                            event.isAltPressed() /* cycle */);
                    return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */,
                            DIRECTION_TAB);
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */);
                    return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, DIRECTION_RIGHT);
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */);
                    return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, DIRECTION_LEFT);
                case KeyEvent.KEYCODE_DPAD_UP:
                    return snapToPageRelative(1, false /* cycle */, DIRECTION_UP);
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    return snapToPageRelative(1, false /* cycle */, DIRECTION_DOWN);
                case KeyEvent.KEYCODE_DEL:
                case KeyEvent.KEYCODE_FORWARD_DEL:
                    dismissCurrentTask();
+510 −0

File added.

Preview size limit exceeded, changes collapsed.