diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index fbd1b6e3da..d84057fe0a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -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; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index d4764c7634..48818362e9 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -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() { diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java index 4b04dba4b4..6ebae49d56 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java @@ -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 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 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()); + } }