diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index ca65d12e87e6e..da5819a7f3bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -90,6 +90,8 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.ListPopupWindow; import android.widget.TextView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; @@ -155,6 +157,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; + private static final boolean DEBUG = false; + private static final String TAG = "GlobalActionsDialogLite"; private static final String INTERACTION_JANK_TAG = "global_actions"; @@ -2177,6 +2181,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected ViewGroup mContainer; + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + logOnBackInvocation(); + dismiss(); + }; + @VisibleForTesting protected GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { @@ -2221,6 +2230,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } }; + + // this exists so that we can point it to a mock during Unit Testing + private OnBackInvokedDispatcher mOverriddenBackDispatcher; + + // the following method exists so that a Unit Test can supply a `OnBackInvokedDispatcher` + @VisibleForTesting + void setBackDispatcherOverride(OnBackInvokedDispatcher mockDispatcher) { + mOverriddenBackDispatcher = mockDispatcher; + } + ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, MyOverflowAdapter overflowAdapter, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, @@ -2254,6 +2273,22 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene super.onCreate(savedInstanceState); initializeLayout(); mWindowDimAmount = getWindow().getAttributes().dimAmount; + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback); + if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler registered"); + } + + @VisibleForTesting + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mOverriddenBackDispatcher != null) return mOverriddenBackDispatcher; + else return super.getOnBackInvokedDispatcher(); + } + + @Override + public void onDetachedFromWindow() { + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler unregistered"); } @Override @@ -2453,7 +2488,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void onBackPressed() { super.onBackPressed(); + logOnBackInvocation(); + } + + private void logOnBackInvocation() { mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK); + if (DEBUG) Log.d(TAG, "onBack invoked"); } @Override diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 763a5cb810f9a..ba2804572ef5c 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -53,7 +53,8 @@ - + mOnBackInvokedCallback; private TestableLooper mTestableLooper; @@ -202,6 +209,58 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK); } + @Test + public void testPredictiveBackCallbackRegisteredAndUnregistered() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + dialog.setBackDispatcherOverride(mOnBackInvokedDispatcher); + dialog.create(); + mTestableLooper.processAllMessages(); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()); + dialog.onDetachedFromWindow(); + mTestableLooper.processAllMessages(); + verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any()); + } + + @Test + public void testPredictiveBackInvocationDismissesDialog() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + dialog.create(); + dialog.show(); + mTestableLooper.processAllMessages(); + dialog.getWindow().injectInputEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)); + dialog.getWindow().injectInputEvent( + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK); + assertThat(dialog.isShowing()).isFalse(); + } + @Test public void testSingleTap_logAndDismiss() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);