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

Commit 54809628 authored by Selim Cinek's avatar Selim Cinek
Browse files

Measuring the Media Views now properly the first time its created

Instead of only knowing the measurement when its attached, we need
to know before otherwise the layout will be broken

Bug: 154137987
Test: atest SystemUITests
Change-Id: I8a4d90a7d933f477df65a5f26942059b0f0920da
parent 3df592e5
Loading
Loading
Loading
Loading
+16 −15
Original line number Diff line number Diff line
@@ -112,6 +112,7 @@ public class MediaControlPanel {
    private int mAlbumArtSize;
    private int mAlbumArtRadius;
    private int mViewWidth;
    private MediaMeasurementInput mLastMeasureInput;

    public static final String MEDIA_PREFERENCES = "media_control_prefs";
    public static final String MEDIA_PREFERENCE_KEY = "browser_components";
@@ -737,31 +738,31 @@ public class MediaControlPanel {
     */
    protected void removePlayer() { }

    public void setDimension(int newWidth, int newHeight, boolean animate, long duration,
    public void remeasure(@Nullable MediaMeasurementInput input, boolean animate, long duration,
            long startDelay) {
        // Let's remeasure if our width changed. Our height is dependent on the expansion, so we
        // won't animate if it changed
        if (newWidth != mViewWidth) {
        if (input != null && !input.sameAs(mLastMeasureInput)) {
            mLastMeasureInput = input;
            if (animate) {
                mLayoutAnimationHelper.animatePendingSizeChange(duration, startDelay);
            }
            setViewWidth(newWidth);
            mMediaNotifView.layout(0, 0, newWidth, mMediaNotifView.getMeasuredHeight());
            remeasureInternal(input);
            mMediaNotifView.layout(0, 0, mMediaNotifView.getMeasuredWidth(),
                    mMediaNotifView.getMeasuredHeight());
        }
    }

    protected void setViewWidth(int newWidth) {
    public MediaMeasurementInput getLastMeasureInput() {
        return mLastMeasureInput;
    }

    private void remeasureInternal(MediaMeasurementInput input) {
        ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded);
        ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed);
        collapsedSet.setGuidelineBegin(R.id.view_width, newWidth);
        expandedSet.setGuidelineBegin(R.id.view_width, newWidth);
        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
        int widthSpec = View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
                View.MeasureSpec.AT_MOST);
        int heightSpec = View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
                View.MeasureSpec.AT_MOST);
        mMediaNotifView.setMinimumWidth(displayMetrics.widthPixels);
        mMediaNotifView.measure(widthSpec, heightSpec);
        mViewWidth = newWidth;
        int width = input.getWidth();
        collapsedSet.setGuidelineBegin(R.id.view_width, width);
        expandedSet.setGuidelineBegin(R.id.view_width, width);
        mMediaNotifView.measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec());
    }
}
+35 −25
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHost
import com.android.systemui.util.animation.UniqueObjectHostView
import javax.inject.Inject
import javax.inject.Singleton

@@ -45,7 +45,8 @@ class MediaHierarchyManager @Inject constructor(
    private val statusBarStateController: SysuiStatusBarStateController,
    private val keyguardStateController: KeyguardStateController,
    private val bypassController: KeyguardBypassController,
    private val mediaViewManager: MediaViewManager
    private val mediaViewManager: MediaViewManager,
    private val mediaMeasurementProvider: MediaMeasurementManager
) {
    private var rootOverlay: ViewGroupOverlay? = null
    private lateinit var currentState: MediaState
@@ -108,7 +109,7 @@ class MediaHierarchyManager @Inject constructor(
     * @return the hostView associated with this location
     */
    fun register(mediaObject: MediaHost) : ViewGroup {
        val viewHost = createUniqueObjectHost()
        val viewHost = createUniqueObjectHost(mediaObject)
        mediaObject.hostView = viewHost;
        mediaHosts[mediaObject.location] = mediaObject
        if (mediaObject.location == desiredLocation) {
@@ -123,8 +124,10 @@ class MediaHierarchyManager @Inject constructor(
        return viewHost
    }

    private fun createUniqueObjectHost(): UniqueObjectHost {
        val viewHost = UniqueObjectHost(context)
    private fun createUniqueObjectHost(host: MediaState): UniqueObjectHostView {
        val viewHost = UniqueObjectHostView(context)
        viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)

        viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(p0: View?) {
                if (rootOverlay == null) {
@@ -148,12 +151,16 @@ class MediaHierarchyManager @Inject constructor(
            val isNewView = this.desiredLocation == -1
            this.desiredLocation = desiredLocation
            // Let's perform a transition
            performTransition(applyImmediately = isNewView)
            val animate = shouldAnimateTransition(desiredLocation, previousLocation)
            val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
            mediaViewManager.onDesiredStateChanged(getHost(desiredLocation)?.currentState,
                    animate, animDuration, delay)
            performTransitionToNewLocation(isNewView, animate)
        }
    }

    private fun performTransition(applyImmediately: Boolean) {
        if (previousLocation < 0 || applyImmediately) {
    private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) {
        if (previousLocation < 0 || isNewView) {
            cancelAnimationAndApplyDesiredState()
            return
        }
@@ -161,29 +168,27 @@ class MediaHierarchyManager @Inject constructor(
        val previousHost = getHost(previousLocation)
        if (currentHost == null || previousHost == null) {
            cancelAnimationAndApplyDesiredState()
            return;
        }
        updateTargetState()
        var animate = false
        if (isCurrentlyInGuidedTransformation()) {
            applyTargetStateIfNotAnimating()
        } else if (shouldAnimateTransition(currentHost, previousHost)) {
        } else if (animate) {
            animator.cancel()
            // Let's animate to the new position, starting from the current position
            animationStartState = currentState.copy()
            adjustAnimatorForTransition(previousLocation, desiredLocation)
            adjustAnimatorForTransition(desiredLocation, previousLocation)
            animator.start()
            animate = true
        } else {
            cancelAnimationAndApplyDesiredState()
        }
        mediaViewManager.performTransition(targetState, animate, animator.duration,
                animator.startDelay)
    }

    private fun shouldAnimateTransition(currentHost: MediaHost, previousHost: MediaHost): Boolean {
        if (currentHost.location == LOCATION_QQS
                && previousHost.location == LOCATION_LOCKSCREEN
    private fun shouldAnimateTransition(
        @MediaLocation currentLocation: Int,
        @MediaLocation previousLocation: Int
    ): Boolean {
        if (currentLocation == LOCATION_QQS
                && previousLocation == LOCATION_LOCKSCREEN
                && (statusBarStateController.leaveOpenOnKeyguardHide()
                        || statusBarStateController.state == StatusBarState.SHADE_LOCKED)) {
            // Usually listening to the isShown is enough to determine this, but there is some
@@ -193,7 +198,16 @@ class MediaHierarchyManager @Inject constructor(
        return mediaCarousel.isShown || animator.isRunning
    }

    private fun adjustAnimatorForTransition(previousLocation: Int, desiredLocation: Int) {
    private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
        val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
        animator.apply {
            duration  = animDuration
            startDelay = delay
        }

    }

    private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
        var animDuration = 200L
        var delay = 0L
        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
@@ -206,11 +220,7 @@ class MediaHierarchyManager @Inject constructor(
        } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
            animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
        }
        animator.apply {
            duration  = animDuration
            startDelay = delay
        }

        return animDuration to delay
    }

    private fun applyTargetStateIfNotAnimating() {
@@ -290,7 +300,7 @@ class MediaHierarchyManager @Inject constructor(
                    boundsOnScreen.bottom)
        }
        currentState = state.copy()
        mediaViewManager.applyState(currentState)
        mediaViewManager.setCurrentState(currentState)
    }

    private fun updateHostAttachment() {
+33 −1
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.view.ViewGroup
import com.android.systemui.media.MediaHierarchyManager.MediaLocation
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementInputData
import javax.inject.Inject

class MediaHost @Inject constructor(
@@ -86,6 +88,7 @@ class MediaHost @Inject constructor(
    }

    class MediaHostState @Inject constructor() : MediaState {
        var measurementInput: MediaMeasurementInput? = null
        override var expansion: Float = 0.0f
        override var showsOnlyActiveMedia: Boolean = false
        override val boundsOnScreen: Rect = Rect()
@@ -95,6 +98,7 @@ class MediaHost @Inject constructor(
            mediaHostState.expansion = expansion
            mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
            mediaHostState.boundsOnScreen.set(boundsOnScreen)
            mediaHostState.measurementInput = measurementInput
            return mediaHostState
        }

@@ -111,8 +115,20 @@ class MediaHost @Inject constructor(
                    other.boundsOnScreen.bottom.toFloat(), amount).toInt()
            result.boundsOnScreen.set(left, top, right, bottom)
            result.showsOnlyActiveMedia = other.showsOnlyActiveMedia || showsOnlyActiveMedia
            if (amount > 0.0f) {
                if (other is MediaHostState) {
                    result.measurementInput = other.measurementInput
                }
            }  else {
                result.measurementInput
            }
            return  result
        }

        override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput {
            measurementInput = MediaMeasurementInput(input, expansion)
            return measurementInput as MediaMeasurementInput
        }
    }
}

@@ -122,4 +138,20 @@ interface MediaState {
    val boundsOnScreen: Rect
    fun copy() : MediaState
    fun interpolate(other: MediaState, amount: Float) : MediaState
    fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput
}
/**
 * The measurement input for a Media View
 */
data class MediaMeasurementInput(
    private val viewInput: MeasurementInput,
    val expansion: Float) : MeasurementInput by viewInput {

    override fun sameAs(input: MeasurementInput?): Boolean {
        if (!(input is MediaMeasurementInput)) {
            return false
        }
        return width == input.width && expansion == input.expansion
    }
}
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.media

import com.android.systemui.util.animation.BaseMeasurementCache
import com.android.systemui.util.animation.GuaranteedMeasurementCache
import com.android.systemui.util.animation.MeasurementCache
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementOutput
import javax.inject.Inject
import javax.inject.Singleton

/**
 * A class responsible creating measurement caches for media hosts which also coordinates with
 * the view manager to obtain sizes for unknown measurement inputs.
 */
@Singleton
class MediaMeasurementManager @Inject constructor(
    private val mediaViewManager: MediaViewManager
) {
    private val baseCache: MeasurementCache

    init {
        baseCache = BaseMeasurementCache()
    }

    private fun provideMeasurement(input: MediaMeasurementInput) : MeasurementOutput? {
        return mediaViewManager.obtainMeasurement(input)
    }

    /**
     * Obtain a guaranteed measurement cache for a host view. The measurement cache makes sure that
     * requesting any size from the cache will always return the correct value.
     */
    fun obtainCache(host: MediaState): GuaranteedMeasurementCache {
        val remapper = { input: MeasurementInput ->
            host.getMeasuringInput(input)
        }
        val provider = { input: MeasurementInput ->
            provideMeasurement(input as MediaMeasurementInput)
        }
        return GuaranteedMeasurementCache(baseCache, remapper, provider)
    }
}
+36 −18
Original line number Diff line number Diff line
@@ -4,7 +4,6 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.LinearLayout

import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.LocalMediaManager
@@ -13,7 +12,8 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.VisualStabilityManager
import com.android.systemui.util.animation.UniqueObjectHost
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -33,12 +33,14 @@ class MediaViewManager @Inject constructor(
    private val activityStarter: ActivityStarter,
    mediaManager: MediaDataManager
) {
    private var desiredState: MediaHost.MediaHostState? = null
    private var currentState: MediaState? = null
    val mediaCarousel: ViewGroup
    private val mediaContent: ViewGroup
    private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
    private val visualStabilityCallback = ::reorderAllPlayers

    private var viewsExpanded = true
    private var currentlyExpanded = true
        set(value) {
            if (field != value) {
                field = value
@@ -68,7 +70,7 @@ class MediaViewManager @Inject constructor(

    private fun inflateMediaCarousel(): ViewGroup {
        return LayoutInflater.from(context).inflate(
                R.layout.media_carousel, UniqueObjectHost(context), false) as ViewGroup
                R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup
    }

    private fun reorderAllPlayers() {
@@ -98,7 +100,7 @@ class MediaViewManager @Inject constructor(
            val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT)
            existingPlayer.view.setLayoutParams(lp)
            existingPlayer.setListening(viewsExpanded)
            existingPlayer.setListening(currentlyExpanded)
            if (existingPlayer.isPlaying) {
                mediaContent.addView(existingPlayer.view, 0)
            } else {
@@ -132,32 +134,48 @@ class MediaViewManager @Inject constructor(

    }

    fun applyState(state: MediaState) {
    fun setCurrentState(state: MediaState) {
        currentState = state
        currentlyExpanded = state.expansion > 0
        for (mediaPlayer in mediaPlayers.values) {
            val view = mediaPlayer.view
            view.progress = state.expansion
        }
        viewsExpanded = state.expansion > 0;
    }

    /**
     * @param targetState the target state we're transitioning to
     * @param animate should this be animated
     */
    fun performTransition(targetState: MediaState?, animate: Boolean, duration: Long,
    fun onDesiredStateChanged(targetState: MediaState?, animate: Boolean, duration: Long,
                              startDelay: Long) {
        if (targetState == null) {
            return
        if (targetState is MediaHost.MediaHostState) {
            // This is a hosting view, let's remeasure our players
            desiredState = targetState
            val measurementInput = targetState.measurementInput
            for (mediaPlayer in mediaPlayers.values) {
                mediaPlayer.remeasure(measurementInput, animate, duration, startDelay)
            }
        val newWidth = targetState.boundsOnScreen.width()
        val newHeight = targetState.boundsOnScreen.height()
        remeasureViews(newWidth, newHeight, animate, duration, startDelay)
        }

    private fun remeasureViews(newWidth: Int, newHeight: Int, animate: Boolean, duration: Long,
                               startDelay: Long) {
        for (mediaPlayer in mediaPlayers.values) {
            mediaPlayer.setDimension(newWidth, newHeight, animate, duration, startDelay)
    }

    /**
     * Get a measurement for the given input state. This measures the first player and returns
     * its bounds as if it were measured with the given measurement dimensions
     */
    fun obtainMeasurement(input: MediaMeasurementInput) : MeasurementOutput? {
        val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null
        // Let's measure the size of the first player and return its height
        val previousProgress = firstPlayer.view.progress
        firstPlayer.view.progress = input.expansion
        firstPlayer.remeasure(input, false /* animate */, 0, 0)
        val result = MeasurementOutput(firstPlayer.view.measuredWidth,
                firstPlayer.view.measuredHeight)
        firstPlayer.view.progress = previousProgress
        if (desiredState != null) {
            // remeasure it to the old size again!
            firstPlayer.remeasure(desiredState!!.measurementInput, false, 0, 0)
        }
        return result
    }
}
 No newline at end of file
Loading