package com.android.systemui.media import android.content.Context import android.os.SystemClock import android.util.AttributeSet import android.view.InputDevice import android.view.MotionEvent import android.view.ViewGroup import android.widget.HorizontalScrollView import com.android.systemui.Gefingerpoken import com.android.wm.shell.animation.physicsAnimator /** * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful * when only measuring children but not the parent, when trying to apply a new scroll position */ class MediaScrollView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : HorizontalScrollView(context, attrs, defStyleAttr) { lateinit var contentContainer: ViewGroup private set var touchListener: Gefingerpoken? = null /** * The target value of the translation X animation. Only valid if the physicsAnimator is running */ var animationTargetX = 0.0f /** * Get the current content translation. This is usually the normal translationX of the content, * but when animating, it might differ */ fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) { animationTargetX } else { contentContainer.translationX } /** * Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX * is always absolute. This function is its own inverse. */ private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) { contentContainer.width - width - scrollX } else { scrollX } /** * Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. */ var relativeScrollX: Int get() = transformScrollX(scrollX) set(value) { scrollX = transformScrollX(value) } /** * Allow all scrolls to go through, use base implementation */ override fun scrollTo(x: Int, y: Int) { if (mScrollX != x || mScrollY != y) { val oldX: Int = mScrollX val oldY: Int = mScrollY mScrollX = x mScrollY = y invalidateParentCaches() onScrollChanged(mScrollX, mScrollY, oldX, oldY) if (!awakenScrollBars()) { postInvalidateOnAnimation() } } } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { var intercept = false touchListener?.let { intercept = it.onInterceptTouchEvent(ev) } return super.onInterceptTouchEvent(ev) || intercept } override fun onTouchEvent(ev: MotionEvent?): Boolean { var touch = false touchListener?.let { touch = it.onTouchEvent(ev) } return super.onTouchEvent(ev) || touch } override fun onFinishInflate() { super.onFinishInflate() contentContainer = getChildAt(0) as ViewGroup } override fun overScrollBy( deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, maxOverScrollY: Int, isTouchEvent: Boolean ): Boolean { if (getContentTranslation() != 0.0f) { // When we're dismissing we ignore all the scrolling return false } return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent) } /** * Cancel the current touch event going on. */ fun cancelCurrentScroll() { val now = SystemClock.uptimeMillis() val event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0) event.source = InputDevice.SOURCE_TOUCHSCREEN super.onTouchEvent(event) event.recycle() } }