Loading quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +7 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -864,17 +864,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; Loading quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +33 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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) Loading Loading @@ -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) { Loading Loading @@ -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. Loading Loading @@ -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() { Loading quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java +53 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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()); } } Loading
quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +7 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -864,17 +864,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; Loading
quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +33 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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) Loading Loading @@ -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) { Loading Loading @@ -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. Loading Loading @@ -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() { Loading
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java +53 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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()); } }