[DO NOT MERGE] Open QS when swiping from status bar in power menu

When there is a downward scroll or fling event that starts in the status bar
area, close the power menu and open the shade (QS if on lockscreen,
notification shade otherwise, to match behavior outside of power menu)
Single tap anywhere outside the dialog will still close it.

Fixes: 190506673
Test: manual
Test: atest GlobalActionsDialogLiteTest
Change-Id: I32239e1751e0e6f14a09e9ab6727e685e8af1008
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index dfd85fe..1fb36d7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -72,6 +72,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -133,7 +134,8 @@
             IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
-            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+            StatusBar statusBar) {
 
         super(context, windowManagerFuncs,
                 audioManager, iDreamManager,
@@ -152,7 +154,7 @@
                 backgroundExecutor,
                 uiEventLogger,
                 null,
-                ringerModeTracker, sysUiState, handler);
+                ringerModeTracker, sysUiState, handler, statusBar);
 
         mLockPatternUtils = lockPatternUtils;
         mKeyguardStateController = keyguardStateController;
@@ -227,7 +229,8 @@
         ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
                 this::getWalletViewController, mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
-                mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger());
+                mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
+                getStatusBar());
 
         if (shouldShowLockMessage(dialog)) {
             dialog.showLockMessage();
@@ -295,11 +298,13 @@
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
-                MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger) {
+                MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
+                StatusBar statusBar) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
                     adapter, overflowAdapter, depthController, sysuiColorExtractor,
                     statusBarService, notificationShadeWindowController, sysuiState,
-                    onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null);
+                    onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null,
+                    statusBar);
             mWalletFactory = walletFactory;
 
             // Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 8e15283..ad920cb 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -74,8 +74,10 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
+import android.view.GestureDetector;
 import android.view.IWindowManager;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -119,6 +121,7 @@
 import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -228,6 +231,7 @@
     private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
     protected Handler mMainHandler;
     private int mSmallestScreenWidthDp;
+    private final StatusBar mStatusBar;
 
     @VisibleForTesting
     public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -322,7 +326,8 @@
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
             GlobalActionsInfoProvider infoProvider,
-            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+            StatusBar statusBar) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -352,6 +357,7 @@
         mSysUiState = sysUiState;
         mMainHandler = handler;
         mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp;
+        mStatusBar = statusBar;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -392,6 +398,10 @@
         return mUiEventLogger;
     }
 
+    protected StatusBar getStatusBar() {
+        return mStatusBar;
+    }
+
     /**
      * Show the global actions dialog (creating if necessary)
      *
@@ -625,7 +635,7 @@
                 mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mInfoProvider);
+                mInfoProvider, mStatusBar);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2100,9 +2110,53 @@
         protected final Runnable mOnRotateCallback;
         private UiEventLogger mUiEventLogger;
         private GlobalActionsInfoProvider mInfoProvider;
+        private GestureDetector mGestureDetector;
+        private StatusBar mStatusBar;
 
         protected ViewGroup mContainer;
 
+        @VisibleForTesting
+        protected GestureDetector.SimpleOnGestureListener mGestureListener =
+                new GestureDetector.SimpleOnGestureListener() {
+                    @Override
+                    public boolean onDown(MotionEvent e) {
+                        // All gestures begin with this message, so continue listening
+                        return true;
+                    }
+
+                    @Override
+                    public boolean onSingleTapConfirmed(MotionEvent e) {
+                        // Close without opening shade
+                        mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+                        cancel();
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                            float distanceY) {
+                        if (distanceY < 0 && distanceY > distanceX
+                                && e1.getY() <= mStatusBar.getStatusBarHeight()) {
+                            // Downwards scroll from top
+                            openShadeAndDismiss();
+                            return true;
+                        }
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                            float velocityY) {
+                        if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX)
+                                && e1.getY() <= mStatusBar.getStatusBarHeight()) {
+                            // Downwards fling from top
+                            openShadeAndDismiss();
+                            return true;
+                        }
+                        return false;
+                    }
+                };
+
         ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
                 MyOverflowAdapter overflowAdapter,
                 NotificationShadeDepthController depthController,
@@ -2110,7 +2164,7 @@
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                @Nullable GlobalActionsInfoProvider infoProvider) {
+                @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
             super(context, themeRes);
             mContext = context;
             mAdapter = adapter;
@@ -2125,6 +2179,9 @@
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
             mInfoProvider = infoProvider;
+            mStatusBar = statusBar;
+
+            mGestureDetector = new GestureDetector(mContext, mGestureListener);
 
             // Window initialization
             Window window = getWindow();
@@ -2146,6 +2203,23 @@
             initializeLayout();
         }
 
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
+        }
+
+        private void openShadeAndDismiss() {
+            mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+            if (mStatusBar.isKeyguardShowing()) {
+                // match existing lockscreen behavior to open QS when swiping from status bar
+                mStatusBar.animateExpandSettingsPanel(null);
+            } else {
+                // otherwise, swiping down should expand notification shade
+                mStatusBar.animateExpandNotificationsPanel();
+            }
+            dismiss();
+        }
+
         private ListPopupWindow createPowerOverflowPopup() {
             GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
                     new ContextThemeWrapper(
@@ -2194,9 +2268,9 @@
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
             mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
-            mContainer.setOnClickListener(v -> {
-                mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
-                cancel();
+            mContainer.setOnTouchListener((v, event) -> {
+                mGestureDetector.onTouchEvent(event);
+                return v.onTouchEvent(event);
             });
 
             View overflowButton = findViewById(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 83e7b17..f0f5420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -38,8 +38,9 @@
 import android.service.dreams.IDreamManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.GestureDetector;
 import android.view.IWindowManager;
-import android.view.View;
+import android.view.MotionEvent;
 import android.view.WindowManagerPolicyConstants;
 
 import androidx.test.filters.SmallTest;
@@ -57,6 +58,7 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -109,6 +111,7 @@
     @Mock private SysUiState mSysUiState;
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
+    @Mock private StatusBar mStatusBar;
 
     private TestableLooper mTestableLooper;
 
@@ -150,7 +153,8 @@
                 mInfoProvider,
                 mRingerModeTracker,
                 mSysUiState,
-                mHandler
+                mHandler,
+                mStatusBar
         );
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
@@ -194,7 +198,7 @@
     }
 
     @Test
-    public void testShouldLogOnTapOutside() {
+    public void testSingleTap_logAndDismiss() {
         mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
         doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
         doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
@@ -207,12 +211,61 @@
         };
         doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
         GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
-        View container = dialog.findViewById(com.android.systemui.R.id.global_actions_container);
-        container.callOnClick();
+
+        GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
+        gestureListener.onSingleTapConfirmed(null);
         verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
     }
 
     @Test
+    public void testSwipeDownLockscreen_logAndOpenQS() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        doReturn(true).when(mStatusBar).isKeyguardShowing();
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+        GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+
+        GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
+        MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
+        gestureListener.onFling(start, end, 0, 1000);
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+        verify(mStatusBar).animateExpandSettingsPanel(null);
+    }
+
+    @Test
+    public void testSwipeDown_logAndOpenNotificationShade() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        doReturn(false).when(mStatusBar).isKeyguardShowing();
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+        GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+
+        GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
+        MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
+        gestureListener.onFling(start, end, 0, 1000);
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+        verify(mStatusBar).animateExpandNotificationsPanel();
+    }
+
+    @Test
     public void testShouldLogBugreportPress() throws InterruptedException {
         GlobalActionsDialog.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 3130e97..c543470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -124,6 +125,7 @@
     @Mock private Handler mHandler;
     @Mock private UserTracker mUserTracker;
     @Mock private SecureSettings mSecureSettings;
+    @Mock private StatusBar mStatusBar;
 
     private TestableLooper mTestableLooper;
 
@@ -164,7 +166,8 @@
                 mUiEventLogger,
                 mRingerModeTracker,
                 mSysUiState,
-                mHandler
+                mHandler,
+                mStatusBar
         );
         mGlobalActionsDialog.setZeroDialogPressDelayForTesting();