diff options
9 files changed, 215 insertions, 5 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 89ad6993cd13..b1d8443a6fb7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -211,6 +211,7 @@ import org.junit.Rule; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; @@ -511,7 +512,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); - View rootView = mock(View.class); + ViewGroup rootView = mock(ViewGroup.class); + when(rootView.isVisibleToUser()).thenReturn(true); when(mView.getRootView()).thenReturn(rootView); when(rootView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); @@ -648,12 +650,21 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); + when(mNotificationShadeWindowController.getWindowRootView()).thenReturn(rootView); doAnswer(invocation -> { mLayoutChangeListener = invocation.getArgument(0); return null; }).when(mView).addOnLayoutChangeListener(any()); when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver); + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + ViewTreeObserver.OnGlobalLayoutListener gll = invocation.getArgument(0); + gll.onGlobalLayout(); + return null; + } + }).when(mViewTreeObserver).addOnGlobalLayoutListener(any()); when(mView.getParent()).thenReturn(mViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); @@ -906,7 +917,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { } protected boolean onTouchEvent(MotionEvent ev) { - return mTouchHandler.onTouch(mView, ev); + return mNotificationPanelViewController.handleExternalTouch(ev); } protected void setDozing(boolean dozing, boolean dozingAlwaysOn) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index ec75972aecfe..550fcf78d857 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -364,6 +364,64 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS) + public void onStatusBarLongPress_shadeExpands() { + long downTime = 42L; + // Start touch session with down event + onTouchEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 1f, 1f, 0)); + // Status bar triggers long press expand + mNotificationPanelViewController.onStatusBarLongPress( + MotionEvent.obtain(downTime, downTime + 27L, MotionEvent.ACTION_MOVE, 1f, 1f, 0)); + assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); + // Shade ignores the rest of the long press's touch session + assertThat(onTouchEvent( + MotionEvent.obtain(downTime, downTime + 42L, MotionEvent.ACTION_MOVE, 1f, 1f, + 0))).isFalse(); + + // Start new touch session + long downTime2 = downTime + 100L; + assertThat(onTouchEvent( + MotionEvent.obtain(downTime2, downTime2, MotionEvent.ACTION_DOWN, 1f, 1f, + 0))).isTrue(); + // Shade no longer ignoring touches + assertThat(onTouchEvent( + MotionEvent.obtain(downTime2, downTime2 + 2L, MotionEvent.ACTION_MOVE, 1f, 1f, + 0))).isTrue(); + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS) + public void onStatusBarLongPress_qsExpands() { + long downTime = 42L; + // Start with shade already expanded + mNotificationPanelViewController.setExpandedFraction(1F); + + // Start touch session with down event + onTouchEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 1f, 1f, 0)); + // Status bar triggers long press expand + mNotificationPanelViewController.onStatusBarLongPress( + MotionEvent.obtain(downTime, downTime + 27L, MotionEvent.ACTION_MOVE, 1f, 1f, 0)); + assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); + // Shade expands to QS + verify(mQsController, atLeastOnce()).flingQs(0F, ShadeViewController.FLING_EXPAND); + // Shade ignores the rest of the long press's touch session + assertThat(onTouchEvent( + MotionEvent.obtain(downTime, downTime + 42L, MotionEvent.ACTION_MOVE, 1f, 1f, + 0))).isFalse(); + + // Start new touch session + long downTime2 = downTime + 100L; + assertThat(onTouchEvent( + MotionEvent.obtain(downTime2, downTime2, MotionEvent.ACTION_DOWN, 1f, 1f, + 0))).isTrue(); + // Shade no longer ignoring touches + assertThat(onTouchEvent( + MotionEvent.obtain(downTime2, downTime2 + 2L, MotionEvent.ACTION_MOVE, 1f, 1f, + 0))).isTrue(); + + } + + @Test @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void test_pulsing_onTouchEvent_noTracking() { // GIVEN device is pulsing diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt new file mode 100644 index 000000000000..6fb3ca5f86d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.content.Context +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */ +@SysUISingleton +class LongPressGestureDetector +@Inject +constructor(context: Context, val shadeViewController: ShadeViewController) { + val gestureDetector = + GestureDetector( + context, + object : SimpleOnGestureListener() { + override fun onLongPress(event: MotionEvent) { + shadeViewController.onStatusBarLongPress(event) + } + }, + ) + + /** Accepts touch events to detect long presses. */ + fun handleTouch(ev: MotionEvent) { + gestureDetector.onTouchEvent(ev) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 083cf1fc8b17..5d6b2e7f94ff 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -363,6 +363,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final TouchHandler mTouchHandler = new TouchHandler(); private long mDownTime; + private long mStatusBarLongPressDowntime; private boolean mTouchSlopExceededBeforeDown; private float mOverExpansion; private CentralSurfaces mCentralSurfaces; @@ -3087,6 +3088,25 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } + /** @deprecated Temporary a11y solution until dual shade launch b/371224114 */ + @Override + @Deprecated + public void onStatusBarLongPress(MotionEvent event) { + mShadeLog.d("Status Bar was long pressed."); + ShadeExpandsOnStatusBarLongPress.assertInNewMode(); + mStatusBarLongPressDowntime = event.getDownTime(); + if (isTracking()) { + onTrackingStopped(true); + } + if (isExpanded() && !mQsController.getExpanded()) { + mShadeLog.d("Status Bar was long pressed. Expanding to QS."); + expandToQs(); + } else { + mShadeLog.d("Status Bar was long pressed. Expanding to Notifications."); + expandToNotifications(); + } + } + @Override public int getBarState() { return mBarState; @@ -3750,6 +3770,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false); mTrackingPointer = -1; + mStatusBarLongPressDowntime = 0L; mAmbientState.setSwipingUp(false); if ((isTracking() && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop || Math.abs(y - mInitialExpandY) > mTouchSlop @@ -5066,6 +5087,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } return true; } + // This touch session has already resulted in shade expansion. Ignore everything else. + if (ShadeExpandsOnStatusBarLongPress.isEnabled() + && event.getActionMasked() != MotionEvent.ACTION_DOWN + && event.getDownTime() == mStatusBarLongPressDowntime) { + mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring."); + return false; + } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); handled = true; @@ -5146,6 +5174,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mUpdateFlingOnLayout = false; mMotionAborted = false; mDownTime = mSystemClock.uptimeMillis(); + mStatusBarLongPressDowntime = 0L; mTouchAboveFalsingThreshold = false; mCollapsedAndHeadsUpOnDown = isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index daea977c9d09..b085aec3de2f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -98,6 +98,10 @@ interface ShadeViewController { /** Returns the ShadeHeadsUpTracker. */ val shadeHeadsUpTracker: ShadeHeadsUpTracker + @Deprecated("Temporary a11y solution until dual shade launch b/371224114") + /** Notifies the shade that a status bar detected a long press gesture. */ + fun onStatusBarLongPress(event: MotionEvent) + /** Returns the ShadeFoldAnimator. */ @Deprecated("This interface is deprecated in Scene Container") val shadeFoldAnimator: ShadeFoldAnimator @@ -179,9 +183,7 @@ interface ShadeViewStateProvider { /** Returns the expanded height of the panel view. */ @Deprecated("deprecated by SceneContainerFlag.isEnabled") val panelViewExpandedHeight: Float - /** - * Returns true if heads up should be visible. - */ + /** Returns true if heads up should be visible. */ @Deprecated("deprecated by SceneContainerFlag.isEnabled.") fun shouldHeadsUpBeVisible(): Boolean /** Return the fraction of the shade that's expanded, when in lockscreen. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 9322d31fa2ce..53617d09fa1c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -35,60 +35,95 @@ open class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeLockscreenInteractor, PanelExpansionInteractor { @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {} + @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false override val isPanelExpanded: Boolean = false + override fun animateCollapseQs(fullyCollapse: Boolean) {} + override fun canBeCollapsed(): Boolean = false + @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false @Deprecated("Use !ShadeInteractor.isAnyExpanded instead") override val isFullyCollapsed: Boolean = false override val isTracking: Boolean = false override val isViewEnabled: Boolean = false + override fun shouldHideStatusBarIconsWhenExpanded() = false + @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {} + override fun startExpandLatencyTracking() {} + override fun startBouncerPreHideAnimation() {} + override fun dozeTimeTick() {} + override fun resetViews(animate: Boolean) {} + override val barState: Int = 0 + @Deprecated("Only supported by very old devices that will not adopt scenes.") override fun closeUserSwitcherIfOpen(): Boolean { return false } + override fun onBackPressed() {} + @Deprecated("According to b/318376223, shade predictive back is not be supported.") override fun onBackProgressed(progressFraction: Float) {} + override fun setAlpha(alpha: Int, animate: Boolean) {} + override fun setAlphaChangeAnimationEndAction(r: Runnable) {} + @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {} + override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {} + override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {} + override fun updateSystemUiStateFlags() {} + override fun updateTouchableRegion() {} + override fun transitionToExpandedShade(delay: Long) {} @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {} + @Deprecated("Not supported by scenes") override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {} + @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {} + @Deprecated("TODO(b/325072511) delete this") override fun setKeyguardStatusBarAlpha(alpha: Float) {} + override fun showAodUi() {} + @Deprecated( "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" + "{@link #isOnAod()}, {@link #isOnKeyguard()} instead." ) override val isFullyExpanded = false + override fun handleExternalTouch(event: MotionEvent): Boolean { return false } + override fun handleExternalInterceptTouch(event: MotionEvent): Boolean { return false } override fun startInputFocusTransfer() {} + override fun cancelInputFocusTransfer() {} + override fun finishInputFocusTransfer(velocity: Float) {} + + @Deprecated("Temporary a11y solution until dual shade launch b/371224114") + override fun onStatusBarLongPress(event: MotionEvent) {} + override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl() override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl() @Deprecated("Use SceneInteractor.currentScene instead.") @@ -98,20 +133,26 @@ open class ShadeViewControllerEmptyImpl @Inject constructor() : class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker { override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {} + override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {} + override fun setHeadsUpAppearanceController( headsUpAppearanceController: HeadsUpAppearanceController? ) {} + override val trackedHeadsUpNotification: ExpandableNotificationRow? = null } class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator { override fun prepareFoldToAodAnimation() {} + override fun startFoldToAodAnimation( startAction: Runnable, endAction: Runnable, cancelAction: Runnable, ) {} + override fun cancelFoldToAodAnimation() {} + override val view: ViewGroup? = null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index e7d9717defa7..91c43ddf1ce4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -39,6 +39,8 @@ import com.android.systemui.Dependency; import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; import com.android.systemui.res.R; +import com.android.systemui.shade.LongPressGestureDetector; +import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; @@ -67,6 +69,7 @@ public class PhoneStatusBarView extends FrameLayout { private InsetsFetcher mInsetsFetcher; private int mDensity; private float mFontScale; + private LongPressGestureDetector mLongPressGestureDetector; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space @@ -78,6 +81,12 @@ public class PhoneStatusBarView extends FrameLayout { mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class); } + void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) { + if (ShadeExpandsOnStatusBarLongPress.isEnabled()) { + mLongPressGestureDetector = longPressGestureDetector; + } + } + void setTouchEventHandler(Gefingerpoken handler) { mTouchEventHandler = handler; } @@ -198,6 +207,9 @@ public class PhoneStatusBarView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) { + mLongPressGestureDetector.handleTouch(event); + } if (mTouchEventHandler == null) { Log.w( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 746d6a75a567..c24f4327f471 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -33,7 +33,9 @@ import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.ui.view.WindowRootView +import com.android.systemui.shade.LongPressGestureDetector import com.android.systemui.shade.ShadeController +import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor @@ -66,6 +68,7 @@ private constructor( private val shadeController: ShadeController, private val shadeViewController: ShadeViewController, private val panelExpansionInteractor: PanelExpansionInteractor, + private val longPressGestureDetector: Provider<LongPressGestureDetector>, private val windowRootView: Provider<WindowRootView>, private val shadeLogger: ShadeLogger, private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, @@ -114,6 +117,10 @@ private constructor( addDarkReceivers() addCursorSupportToIconContainers() + if (ShadeExpandsOnStatusBarLongPress.isEnabled) { + mView.setLongPressGestureDetector(longPressGestureDetector.get()) + } + progressProvider?.setReadyToHandleTransition(true) configurationController.addCallback(configurationListener) @@ -328,6 +335,7 @@ private constructor( private val shadeController: ShadeController, private val shadeViewController: ShadeViewController, private val panelExpansionInteractor: PanelExpansionInteractor, + private val longPressGestureDetector: Provider<LongPressGestureDetector>, private val windowRootView: Provider<WindowRootView>, private val shadeLogger: ShadeLogger, private val viewUtil: ViewUtil, @@ -352,6 +360,7 @@ private constructor( shadeController, shadeViewController, panelExpansionInteractor, + longPressGestureDetector, windowRootView, shadeLogger, statusBarMoveFromCenterAnimationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 008e8ce92736..638f195df00c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.fakeDarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView +import com.android.systemui.shade.LongPressGestureDetector import com.android.systemui.shade.ShadeControllerImpl import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController @@ -97,6 +98,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var windowRootView: Provider<WindowRootView> @Mock private lateinit var shadeLogger: ShadeLogger @Mock private lateinit var viewUtil: ViewUtil + @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector private lateinit var statusBarWindowStateController: StatusBarWindowStateController private lateinit var view: PhoneStatusBarView @@ -393,6 +395,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { shadeControllerImpl, shadeViewController, panelExpansionInteractor, + { longPressGestureDetector }, windowRootView, shadeLogger, viewUtil, |