From 1dde50349e9a267ce2f3a3700f6d7acf31e5544c Mon Sep 17 00:00:00 2001 From: Rahul Banerjee Date: Thu, 18 Aug 2022 10:18:51 -0700 Subject: [PATCH] Migrate GlobalActionsDialogLite to predictive back As part of the effort to rollout Predictive Back, the old "just in time" back handlers need to be migrated over to the new "ahead of time" api (before any animation/feedback can be added). This is the first change in this API migration, with more to come. This also adds tests for GlobalActionsDialogLite. Bug: 238358031 Test: atest GlobalActionsDialogLiteTest Change-Id: Id8ee4ec97e34e6de63659d30784384e7c9ef9590 --- .../GlobalActionsDialogLite.java | 40 +++++++++++++ packages/SystemUI/tests/AndroidManifest.xml | 3 +- .../GlobalActionsDialogLiteTest.java | 59 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) 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);