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

Commit a4b56465 authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

Disable 3-button-nav buttons during back button hold

Bug: 373544911
Test: TaskbarNavButtonControllerTest
Test: Manual, i.e. verify that pressing buttons in 3-button-nav while the back button is pressed does not have any effect
Flag: com.android.window.flags.predictive_back_three_button_nav
Change-Id: I5abad5f2f74d09c790380a2eeb27aff3b780b925
parent 7241c8c5
Loading
Loading
Loading
Loading
+7 −12
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.launcher3.taskbar;

import static android.view.KeyEvent.ACTION_UP;
import static android.view.View.AccessibilityDelegate;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -72,7 +73,6 @@ import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.RotateDrawable;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Property;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -862,17 +862,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
            TaskbarNavButtonController navButtonController) {
        buttonView.setOnTouchListener((v, event) -> {
            if (event.getAction() == MotionEvent.ACTION_MOVE) return false;
            long time = SystemClock.uptimeMillis();
            int action = event.getAction();
            KeyEvent keyEvent = new KeyEvent(time, time,
                    action == MotionEvent.ACTION_DOWN ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
                    KeyEvent.KEYCODE_BACK, 0);
            if (event.getAction() == MotionEvent.ACTION_CANCEL) {
                keyEvent.cancel();
            }
            navButtonController.executeBack(keyEvent);

            if (action == MotionEvent.ACTION_UP) {
            int motionEventAction = event.getAction();
            int keyEventAction = motionEventAction == MotionEvent.ACTION_DOWN
                    ? KeyEvent.ACTION_DOWN : ACTION_UP;
            boolean isCancelled = event.getAction() == MotionEvent.ACTION_CANCEL;
            navButtonController.sendBackKeyEvent(keyEventAction, isCancelled);
            if (motionEventAction == MotionEvent.ACTION_UP) {
                buttonView.performClick();
            }
            return false;
+33 −2
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@

package com.android.launcher3.taskbar;

import static android.view.MotionEvent.ACTION_UP;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.ACTION_UP;

import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
@@ -38,6 +39,7 @@ import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -78,6 +80,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
    private long mLastScreenPinLongPress;
    private boolean mScreenPinned;
    private boolean mAssistantLongPressEnabled;
    private int mLastSentBackAction = ACTION_UP;

    @Override
    public void dumpLogs(String prefix, PrintWriter pw) {
@@ -85,6 +88,8 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa

        pw.println(prefix + "\tmLastScreenPinLongPress=" + mLastScreenPinLongPress);
        pw.println(prefix + "\tmScreenPinned=" + mScreenPinned);
        pw.println(prefix + "\tmLastSentBackAction="
                + KeyEvent.actionToString(mLastSentBackAction));
    }

    @Retention(RetentionPolicy.SOURCE)
@@ -141,6 +146,11 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
        if (buttonType == BUTTON_SPACE) {
            return;
        }
        if (predictiveBackThreeButtonNav() && mLastSentBackAction == ACTION_DOWN) {
            Log.i(TAG, "Button click ignored while back button is pressed");
            // prevent interactions with other buttons while back button is pressed
            return;
        }
        // Provide the same haptic feedback that the system offers for virtual keys.
        view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
        switch (buttonType) {
@@ -180,6 +190,13 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
        if (buttonType == BUTTON_SPACE) {
            return false;
        }
        if (predictiveBackThreeButtonNav() && mLastSentBackAction == ACTION_DOWN
                && buttonType != BUTTON_BACK && buttonType != BUTTON_RECENTS) {
            // prevent interactions with other buttons while back button is pressed (except back
            // and recents button for screen-unpin action).
            Log.i(TAG, "Button long click ignored while back button is pressed");
            return false;
        }

        // Provide the same haptic feedback that the system offers for long press.
        // The haptic feedback from long pressing on the home button is handled by circle to search.
@@ -327,13 +344,27 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
        mCallbacks.onToggleOverview();
    }

    void executeBack(@Nullable KeyEvent keyEvent) {
    void sendBackKeyEvent(int action, boolean cancelled) {
        if (action == mLastSentBackAction) {
            // There must always be an alternating sequence of ACTION_DOWN and ACTION_UP events
            return;
        }
        long time = SystemClock.uptimeMillis();
        KeyEvent keyEvent = new KeyEvent(time, time, action, KeyEvent.KEYCODE_BACK, 0);
        if (cancelled) {
            keyEvent.cancel();
        }
        executeBack(keyEvent);
    }

    private void executeBack(@Nullable KeyEvent keyEvent) {
        if (keyEvent == null || (keyEvent.getAction() == ACTION_UP && !keyEvent.isCanceled())) {
            logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
            mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
                    GestureType.BACK);
        }
        mSystemUiProxy.onBackEvent(keyEvent);
        mLastSentBackAction = keyEvent != null ? keyEvent.getAction() : ACTION_UP;
    }

    private void onImeSwitcherPress() {
+53 −0
Original line number Diff line number Diff line
@@ -15,9 +15,11 @@ import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IM
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -28,6 +30,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.Handler;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.Flags;

@@ -43,8 +49,10 @@ import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.systemui.contextualeducation.GestureType;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@@ -76,6 +84,9 @@ public class TaskbarNavButtonControllerTest {
    @Mock
    View mockView;

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    private int mHomePressCount;
    private int mOverviewToggleCount;
    private final TaskbarNavButtonCallbacks mCallbacks = new TaskbarNavButtonCallbacks() {
@@ -333,4 +344,46 @@ public class TaskbarNavButtonControllerTest {
        verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
        verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV)
    public void testPredictiveBackInvoked() {
        ArgumentCaptor<KeyEvent> keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class);
        mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false);
        mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false);
        verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture());
        verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false);
        verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_UP, false);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV)
    public void testPredictiveBackCancelled() {
        ArgumentCaptor<KeyEvent> keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class);
        mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false);
        mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, true);
        verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture());
        verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false);
        verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_UP, true);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV)
    public void testButtonsDisabledWhileBackPressed() {
        mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false);
        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
        mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
        mNavButtonController.onButtonLongClick(BUTTON_A11Y, mockView);
        mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
        mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false);
        assertThat(mHomePressCount).isEqualTo(0);
        verify(mockSystemUiProxy, never()).notifyAccessibilityButtonLongClicked();
        assertThat(mOverviewToggleCount).isEqualTo(0);
        verify(mockSystemUiProxy, never()).onImeSwitcherPressed();
    }

    private void verifyKeyEvent(KeyEvent keyEvent, int action, boolean isCancelled) {
        assertEquals(isCancelled, keyEvent.isCanceled());
        assertEquals(action, KeyEvent.ACTION_DOWN, keyEvent.getAction());
    }
}