Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 974818bb authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Fix MediaCarousel in RTL" into rvc-dev am: b01bd6a5

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11988335

Change-Id: I162d5fd77b77b7d3c938b2e060e9eafa9d83c83f
parents 4d44e8ec b01bd6a5
Loading
Loading
Loading
Loading
+36 −10
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ package com.android.systemui.media

import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.view.LayoutInflater
@@ -96,7 +97,6 @@ class MediaCarouselController @Inject constructor(
     * The measured height of the carousel
     */
    private var carouselMeasureHeight: Int = 0
    private var playerWidthPlusPadding: Int = 0
    private var desiredHostState: MediaHostState? = null
    private val mediaCarousel: MediaScrollView
    private val mediaCarouselScrollHandler: MediaCarouselScrollHandler
@@ -108,6 +108,15 @@ class MediaCarouselController @Inject constructor(
    private val pageIndicator: PageIndicator
    private val visualStabilityCallback: VisualStabilityManager.Callback
    private var needsReordering: Boolean = false
    private var isRtl: Boolean = false
        set(value) {
            if (value != field) {
                field = value
                mediaFrame.layoutDirection =
                        if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
                mediaCarouselScrollHandler.scrollToStart()
            }
        }
    private var currentlyExpanded = true
        set(value) {
            if (field != value) {
@@ -126,6 +135,11 @@ class MediaCarouselController @Inject constructor(
        override fun onOverlayChanged() {
            inflateSettingsButton()
        }

        override fun onConfigChanged(newConfig: Configuration?) {
            if (newConfig == null) return
            isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
        }
    }

    init {
@@ -135,6 +149,7 @@ class MediaCarouselController @Inject constructor(
        mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
                executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
                falsingManager)
        isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
        inflateSettingsButton()
        mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
        configurationController.addCallback(configListener)
@@ -144,7 +159,7 @@ class MediaCarouselController @Inject constructor(
                reorderAllPlayers()
            }
            // Let's reset our scroll position
            mediaCarousel.scrollX = 0
            mediaCarouselScrollHandler.scrollToStart()
        }
        visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
                true /* persistent */)
@@ -196,8 +211,13 @@ class MediaCarouselController @Inject constructor(
    }

    private fun inflateMediaCarousel(): ViewGroup {
        return LayoutInflater.from(context).inflate(R.layout.media_carousel,
        val mediaCarousel = LayoutInflater.from(context).inflate(R.layout.media_carousel,
                UniqueObjectHostView(context), false) as ViewGroup
        // Because this is inflated when not attached to the true view hierarchy, it resolves some
        // potential issues to force that the layout direction is defined by the locale
        // (rather than inherited from the parent, which would resolve to LTR when unattached).
        mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
        return mediaCarousel
    }

    private fun reorderAllPlayers() {
@@ -313,8 +333,12 @@ class MediaCarouselController @Inject constructor(

    private fun updatePageIndicatorLocation() {
        // Update the location of the page indicator, carousel clipping
        pageIndicator.translationX = (currentCarouselWidth - pageIndicator.width) / 2.0f +
                mediaCarouselScrollHandler.contentTranslation
        val translationX = if (isRtl) {
            (pageIndicator.width - currentCarouselWidth) / 2.0f
        } else {
            (currentCarouselWidth - pageIndicator.width) / 2.0f
        }
        pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
        val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
        pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height -
                layoutParams.bottomMargin).toFloat()
@@ -334,7 +358,8 @@ class MediaCarouselController @Inject constructor(
        if (width != currentCarouselWidth || height != currentCarouselHeight) {
            currentCarouselWidth = width
            currentCarouselHeight = height
            mediaCarouselScrollHandler.setCarouselBounds(currentCarouselWidth, currentCarouselHeight)
            mediaCarouselScrollHandler.setCarouselBounds(
                    currentCarouselWidth, currentCarouselHeight)
            updatePageIndicatorLocation()
        }
    }
@@ -348,7 +373,7 @@ class MediaCarouselController @Inject constructor(
        if (currentlyShowingOnlyActive != endShowsActive ||
                ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
                            startShowsActive != endShowsActive)) {
            /// Whenever we're transitioning from between differing states or the endstate differs
            // Whenever we're transitioning from between differing states or the endstate differs
            // we reset the translation
            currentlyShowingOnlyActive = endShowsActive
            mediaCarouselScrollHandler.resetTranslation(animate = true)
@@ -416,14 +441,15 @@ class MediaCarouselController @Inject constructor(
                height != carouselMeasureWidth && height != 0) {
            carouselMeasureWidth = width
            carouselMeasureHeight = height
            playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize(
                    R.dimen.qs_media_padding)
            mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
            val playerWidthPlusPadding = carouselMeasureWidth +
                    context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
            // Let's remeasure the carousel
            val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
            val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
            mediaCarousel.measure(widthSpec, heightSpec)
            mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
            // Update the padding after layout; view widths are used in RTL to calculate scrollX
            mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
        }
    }
}
+68 −32
Original line number Diff line number Diff line
@@ -58,6 +58,10 @@ class MediaCarouselScrollHandler(
    private var translationChangedListener: () -> Unit,
    private val falsingManager: FalsingManager
) {
    /**
     * Is the view in RTL
     */
    val isRtl: Boolean get() = scrollView.isLayoutRtl
    /**
     * Do we need falsing protection?
     */
@@ -121,14 +125,14 @@ class MediaCarouselScrollHandler(
            field = value
            // The player width has changed, let's update the scroll position to make sure
            // it's still at the same place
            var newScroll = activeMediaIndex * playerWidthPlusPadding
            var newRelativeScroll = activeMediaIndex * playerWidthPlusPadding
            if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
                newScroll += playerWidthPlusPadding -
                newRelativeScroll += playerWidthPlusPadding -
                        (scrollIntoCurrentMedia - playerWidthPlusPadding)
            } else {
                newScroll += scrollIntoCurrentMedia
                newRelativeScroll += scrollIntoCurrentMedia
            }
            scrollView.scrollX = newScroll
            scrollView.relativeScrollX = newRelativeScroll
        }

    /**
@@ -184,8 +188,9 @@ class MediaCarouselScrollHandler(
            if (playerWidthPlusPadding == 0) {
                return
            }
            onMediaScrollingChanged(scrollX / playerWidthPlusPadding,
                    scrollX % playerWidthPlusPadding)
            val relativeScrollX = scrollView.relativeScrollX
            onMediaScrollingChanged(relativeScrollX / playerWidthPlusPadding,
                    relativeScrollX % playerWidthPlusPadding)
        }
    }

@@ -222,11 +227,19 @@ class MediaCarouselScrollHandler(
                    Math.abs(contentTranslation))
            val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width *
                    SETTINGS_BUTTON_TRANSLATION_FRACTION
            val newTranslationX: Float
            val newTranslationX = if (isRtl) {
                // In RTL, the 0-placement is on the right side of the view, not the left...
                if (contentTranslation > 0) {
                    -(scrollView.width - settingsTranslation - settingsButton.width)
                } else {
                    -settingsTranslation
                }
            } else {
                if (contentTranslation > 0) {
                newTranslationX = settingsTranslation
                    settingsTranslation
                } else {
                newTranslationX = scrollView.width - settingsTranslation - settingsButton.width
                    scrollView.width - settingsTranslation - settingsButton.width
                }
            }
            val rotation = (1.0f - settingsOffset) * 50
            settingsButton.rotation = rotation * -Math.signum(contentTranslation)
@@ -259,26 +272,26 @@ class MediaCarouselScrollHandler(
        }
        if (isUp || motionEvent.action == MotionEvent.ACTION_CANCEL) {
            // It's an up and the fling didn't take it above
            val pos = scrollView.scrollX % playerWidthPlusPadding
            val scollXAmount: Int
            if (pos > playerWidthPlusPadding / 2) {
                scollXAmount = playerWidthPlusPadding - pos
            val relativePos = scrollView.relativeScrollX % playerWidthPlusPadding
            val scrollXAmount: Int
            if (relativePos > playerWidthPlusPadding / 2) {
                scrollXAmount = playerWidthPlusPadding - relativePos
            } else {
                scollXAmount = -1 * pos
                scrollXAmount = -1 * relativePos
            }
            if (scollXAmount != 0) {
            if (scrollXAmount != 0) {
                // Delay the scrolling since scrollView calls springback which cancels
                // the animation again..
                mainExecutor.execute {
                    scrollView.smoothScrollBy(scollXAmount, 0)
                    scrollView.smoothScrollBy(if (isRtl) -scrollXAmount else scrollXAmount, 0)
                }
            }
            val currentTranslation = scrollView.getContentTranslation()
            if (currentTranslation != 0.0f) {
                // We started a Swipe but didn't end up with a fling. Let's either go to the
                // dismissed position or go back.
                val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2
                        || isFalseTouch()
                val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 ||
                        isFalseTouch()
                val newTranslation: Float
                if (springBack) {
                    newTranslation = 0.0f
@@ -313,9 +326,11 @@ class MediaCarouselScrollHandler(
        return gestureDetector.onTouchEvent(motionEvent)
    }

    fun onScroll(down: MotionEvent,
    fun onScroll(
        down: MotionEvent,
        lastMotion: MotionEvent,
                 distanceX: Float): Boolean {
        distanceX: Float
    ): Boolean {
        val totalX = lastMotion.x - down.x
        val currentTranslation = scrollView.getContentTranslation()
        if (currentTranslation != 0.0f ||
@@ -339,8 +354,8 @@ class MediaCarouselScrollHandler(
                } // Otherwise we don't have do do anything, and will remove the unrubberbanded
                // translation
            }
            if (Math.signum(newTranslation) != Math.signum(currentTranslation)
                    && currentTranslation != 0.0f) {
            if (Math.signum(newTranslation) != Math.signum(currentTranslation) &&
                    currentTranslation != 0.0f) {
                // We crossed the 0.0 threshold of the translation. Let's see if we're allowed
                // to scroll into the new direction
                if (scrollView.canScrollHorizontally(-newTranslation.toInt())) {
@@ -394,9 +409,10 @@ class MediaCarouselScrollHandler(
            scrollView.animationTargetX = newTranslation
        } else {
            // We're flinging the player! Let's go either to the previous or to the next player
            val pos = scrollView.scrollX
            val pos = scrollView.relativeScrollX
            val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0
            var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex
            val flungTowardEnd = if (isRtl) vX > 0 else vX < 0
            var destIndex = if (flungTowardEnd) currentIndex + 1 else currentIndex
            destIndex = Math.max(0, destIndex)
            destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
            val view = mediaContent.getChildAt(destIndex)
@@ -438,8 +454,14 @@ class MediaCarouselScrollHandler(
            activeMediaIndex = newIndex
            updatePlayerVisibilities()
        }
        val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
        val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
            scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
        // Fix the location, because PageIndicator does not handle RTL internally
        val location = if (isRtl) {
            mediaContent.childCount - relativeLocation - 1
        } else {
            relativeLocation
        }
        pageIndicator.setLocation(location)
        updateClipToOutline()
    }
@@ -480,13 +502,20 @@ class MediaCarouselScrollHandler(
     * where it was and update our scroll position.
     */
    fun onPrePlayerRemoved(removed: MediaControlPanel) {
        val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= activeMediaIndex
        val removedIndex = mediaContent.indexOfChild(removed.view?.player)
        // If the removed index is less than the activeMediaIndex, then we need to decrement it.
        // RTL has no effect on this, because indices are always relative (start-to-end).
        // Update the index 'manually' since we won't always get a call to onMediaScrollingChanged
        val beforeActive = removedIndex <= activeMediaIndex
        if (beforeActive) {
            // also update the index here since the scroll below might not always lead
            // to a scrolling changed
            activeMediaIndex = Math.max(0, activeMediaIndex - 1)
            scrollView.scrollX = Math.max(scrollView.scrollX -
                    playerWidthPlusPadding, 0)
        }
        // If the removed media item is "left of" the active one (in an absolute sense), we need to
        // scroll the view to keep that player in view.  This is because scroll position is always
        // calculated from left to right.
        val leftOfActive = if (isRtl) !beforeActive else beforeActive
        if (leftOfActive) {
            scrollView.scrollX = Math.max(scrollView.scrollX - playerWidthPlusPadding, 0)
        }
    }

@@ -501,6 +530,13 @@ class MediaCarouselScrollHandler(
        }
    }

    /**
     * Reset the MediaScrollView to the start.
     */
    fun scrollToStart() {
        scrollView.relativeScrollX = 0
    }

    companion object {
        private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>(
                "contentTranslation") {
+38 −7
Original line number Diff line number Diff line
@@ -15,7 +15,10 @@ import com.android.systemui.util.animation.physicsAnimator
 * 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)
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
)
    : HorizontalScrollView(context, attrs, defStyleAttr) {

    lateinit var contentContainer: ViewGroup
@@ -37,6 +40,26 @@ class MediaScrollView @JvmOverloads constructor(
        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
     */
@@ -55,15 +78,15 @@ class MediaScrollView @JvmOverloads constructor(
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        var intercept = false;
        var intercept = false
        touchListener?.let {
            intercept = it.onInterceptTouchEvent(ev)
        }
        return super.onInterceptTouchEvent(ev) || intercept;
        return super.onInterceptTouchEvent(ev) || intercept
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        var touch = false;
        var touch = false
        touchListener?.let {
            touch = it.onTouchEvent(ev)
        }
@@ -75,9 +98,17 @@ class MediaScrollView @JvmOverloads constructor(
        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 {
    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