diff options
| author | 2024-05-09 21:55:22 +0000 | |
|---|---|---|
| committer | 2024-05-13 19:57:17 +0000 | |
| commit | 734129960386e37d634f8ad411a367038565bed9 (patch) | |
| tree | 217e119b889917a6ba975207a5898620d12cd34a | |
| parent | 55e53393ec8f3143014d756f174ac669cfe21d45 (diff) | |
Fix ScreenshotShelfView gesture handling
We didn't previously have logic to decide whether to scroll "within"
the actions container vs. swiping away the screenshots UI. This CL
adapts the legacy solution of using an internal GestureDetector to
classify gestures as eligible for interception only if we're unable
to interpret them as scrolls of the action container.
Bug: 339105691, 332406583
Test: manual interactions w/ extra instrumentation
Flag: ACONFIG com.android.systemui.screenshot_shelf_ui_2 DEVELOPMENT
Change-Id: I25103b5791f30d9e07dfc27b5d9fd5fcf9c81014
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt | 57 |
1 files changed, 52 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt index bd932260848b..969cf482be90 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt @@ -22,6 +22,8 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -43,13 +45,43 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private val displayMetrics = context.resources.displayMetrics private val tmpRect = Rect() private lateinit var actionsContainerBackground: View + private lateinit var actionsContainer: View private lateinit var dismissButton: View + // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception + // session (with the client's provided `onTouchInterceptListener`). We delegate out to their + // listener only for gestures that can't be handled by scrolling our `actionsContainer`. + private val gestureDetector = + GestureDetector( + context, + object : SimpleOnGestureListener() { + override fun onScroll( + ev1: MotionEvent?, + ev2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + actionsContainer.getBoundsOnScreen(tmpRect) + val touchedInActionsContainer = + tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt()) + val canHandleInternallyByScrolling = + touchedInActionsContainer + && actionsContainer.canScrollHorizontally(distanceX.toInt()) + return !canHandleInternallyByScrolling + } + } + ) + init { - setOnTouchListener({ _: View, _: MotionEvent -> + + // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated + // touch-interception. + setOnTouchListener({ _: View, ev: MotionEvent -> userInteractionCallback?.invoke() - true + onTouchInterceptListener?.invoke(ev) ?: false }) + + gestureDetector.setIsLongpressEnabled(false) } override fun onFinishInflate() { @@ -60,7 +92,15 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur) screenshotStatic = requireViewById(R.id.screenshot_static) actionsContainerBackground = requireViewById(R.id.actions_container_background) + actionsContainer = requireViewById(R.id.actions_container) dismissButton = requireViewById(R.id.screenshot_dismiss_button) + + // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already + // being handled by our child views. + actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + false + }) } fun getTouchRegion(gestureInsets: Insets): Region { @@ -171,9 +211,16 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { userInteractionCallback?.invoke() - if (onTouchInterceptListener?.invoke(ev) == true) { - return true + // Let the client-provided listener see all `DOWN` events so that they'll be able to + // interpret the remainder of the gesture, even if interception starts partway-through. + // TODO: is this really necessary? And if we don't go on to start interception, should we + // follow up with `ACTION_CANCEL`? + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + onTouchInterceptListener?.invoke(ev) } - return super.onInterceptTouchEvent(ev) + + // Only allow the client-provided touch interceptor to take over the gesture if our + // top-level `GestureDetector` decides not to scroll the action container. + return gestureDetector.onTouchEvent(ev) } } |