Make media squishy for portrait shade open and close
Propagate squishFraction from QSFragment to MediaHostState.
In MediaViewController, apply squishFraction to both
cached and uncached UMO viewStates.
Remove mTopPadding (qsHeight / space above lockscreen notifs)
from expansionFraction (aka squishFraction) calculations to avoid
the following recursion:
=> squishFraction changes UMO height
=> new UMO height changes mTopPadding
=> mTopPadding changes total stack height
=> new stack height changes squishFraction
Use expandedFraction calculated in PVC instead of recalculating it
in NSSL.
Bug: 217583474
Test: MediaViewControllerTest
Test: open/close portrait shade => squishy UMO
Change-Id: Ifdada015563394a96c3d80ae527c68ac7323d4c2
(cherry picked from commit a24659d135)
Merged-In: Ifdada015563394a96c3d80ae527c68ac7323d4c2
This commit is contained in:
@@ -192,6 +192,14 @@ class MediaHost constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override var squishFraction: Float = 1.0f
|
||||
set(value) {
|
||||
if (!value.equals(field)) {
|
||||
field = value
|
||||
changedListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
override var showsOnlyActiveMedia: Boolean = false
|
||||
set(value) {
|
||||
if (!value.equals(field)) {
|
||||
@@ -242,6 +250,7 @@ class MediaHost constructor(
|
||||
override fun copy(): MediaHostState {
|
||||
val mediaHostState = MediaHostStateHolder()
|
||||
mediaHostState.expansion = expansion
|
||||
mediaHostState.squishFraction = squishFraction
|
||||
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
|
||||
mediaHostState.measurementInput = measurementInput?.copy()
|
||||
mediaHostState.visible = visible
|
||||
@@ -260,6 +269,9 @@ class MediaHost constructor(
|
||||
if (expansion != other.expansion) {
|
||||
return false
|
||||
}
|
||||
if (squishFraction != other.squishFraction) {
|
||||
return false
|
||||
}
|
||||
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
|
||||
return false
|
||||
}
|
||||
@@ -278,6 +290,7 @@ class MediaHost constructor(
|
||||
override fun hashCode(): Int {
|
||||
var result = measurementInput?.hashCode() ?: 0
|
||||
result = 31 * result + expansion.hashCode()
|
||||
result = 31 * result + squishFraction.hashCode()
|
||||
result = 31 * result + falsingProtectionNeeded.hashCode()
|
||||
result = 31 * result + showsOnlyActiveMedia.hashCode()
|
||||
result = 31 * result + if (visible) 1 else 2
|
||||
@@ -317,6 +330,11 @@ interface MediaHostState {
|
||||
*/
|
||||
var expansion: Float
|
||||
|
||||
/**
|
||||
* Fraction of the height animation.
|
||||
*/
|
||||
var squishFraction: Float
|
||||
|
||||
/**
|
||||
* Is this host only showing active media or is it showing all of them including resumption?
|
||||
*/
|
||||
|
||||
@@ -18,13 +18,11 @@ package com.android.systemui.media
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.animation.MeasurementOutput
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
import com.android.systemui.util.animation.TransitionLayoutController
|
||||
import com.android.systemui.util.animation.TransitionViewState
|
||||
import com.android.systemui.util.animation.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -270,7 +268,6 @@ class MediaViewController @Inject constructor(
|
||||
TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.gutsIds
|
||||
TYPE.RECOMMENDATION -> RecommendationViewHolder.gutsIds
|
||||
}
|
||||
|
||||
controlsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.let { state ->
|
||||
// Make sure to use the unmodified state if guts are not visible.
|
||||
@@ -282,59 +279,79 @@ class MediaViewController @Inject constructor(
|
||||
viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
|
||||
viewState.widgetStates.get(id)?.gone = !isGutsVisible
|
||||
}
|
||||
|
||||
if (shouldHideGutsSettings) {
|
||||
viewState.widgetStates.get(R.id.settings)?.gone = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply squishFraction to a copy of viewState such that the cached version is untouched.
|
||||
*/
|
||||
private fun squishViewState(viewState: TransitionViewState,
|
||||
squishFraction: Float): TransitionViewState {
|
||||
val squishedViewState = viewState.copy()
|
||||
squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
|
||||
val albumArtViewState = viewState.widgetStates.get(R.id.album_art)
|
||||
if (albumArtViewState != null) {
|
||||
albumArtViewState.height = squishedViewState.height
|
||||
}
|
||||
return squishedViewState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
|
||||
* it's not available, it will recreate one by measuring, which may be expensive.
|
||||
*/
|
||||
private fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
||||
@VisibleForTesting
|
||||
public fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
||||
if (state == null || state.measurementInput == null) {
|
||||
return null
|
||||
}
|
||||
// Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
|
||||
var cacheKey = getKey(state, isGutsVisible, tmpKey)
|
||||
val viewState = viewStates[cacheKey]
|
||||
|
||||
if (viewState != null) {
|
||||
// we already have cached this measurement, let's continue
|
||||
if (state.squishFraction < 1f) {
|
||||
return squishViewState(viewState, state.squishFraction);
|
||||
}
|
||||
return viewState
|
||||
}
|
||||
// Copy the key since this might call recursively into it and we're using tmpKey
|
||||
cacheKey = cacheKey.copy()
|
||||
val result: TransitionViewState?
|
||||
if (transitionLayout != null) {
|
||||
// Let's create a new measurement
|
||||
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
||||
result = transitionLayout!!.calculateViewState(
|
||||
state.measurementInput!!,
|
||||
constraintSetForExpansion(state.expansion),
|
||||
TransitionViewState())
|
||||
|
||||
setGutsViewState(result)
|
||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
||||
// our cache. We only cache the start and the end states since the interpolation
|
||||
// is cheap
|
||||
viewStates[cacheKey] = result
|
||||
} else {
|
||||
// This is an interpolated state
|
||||
val startState = state.copy().also { it.expansion = 0.0f }
|
||||
|
||||
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
|
||||
// from the start and end state and interpolate them
|
||||
val startViewState = obtainViewState(startState) as TransitionViewState
|
||||
val endState = state.copy().also { it.expansion = 1.0f }
|
||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||
result = layoutController.getInterpolatedState(
|
||||
startViewState,
|
||||
endViewState,
|
||||
state.expansion)
|
||||
}
|
||||
if (transitionLayout == null) {
|
||||
return null
|
||||
}
|
||||
// Not cached. Let's create a new measurement
|
||||
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
||||
result = transitionLayout!!.calculateViewState(
|
||||
state.measurementInput!!,
|
||||
constraintSetForExpansion(state.expansion),
|
||||
TransitionViewState())
|
||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
||||
// our cache. We only cache the start and the end states since the interpolation
|
||||
// is cheap
|
||||
setGutsViewState(result)
|
||||
viewStates[cacheKey] = result
|
||||
} else {
|
||||
result = null
|
||||
// This is an interpolated state
|
||||
val startState = state.copy().also { it.expansion = 0.0f }
|
||||
|
||||
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
|
||||
// from the start and end state and interpolate them
|
||||
val startViewState = obtainViewState(startState) as TransitionViewState
|
||||
val endState = state.copy().also { it.expansion = 1.0f }
|
||||
|
||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||
result = layoutController.getInterpolatedState(
|
||||
startViewState,
|
||||
endViewState,
|
||||
state.expansion)
|
||||
}
|
||||
if (state.squishFraction < 1f) {
|
||||
return squishViewState(result, state.squishFraction);
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -573,6 +573,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
if (mQSAnimator != null) {
|
||||
mQSAnimator.setPosition(expansion);
|
||||
}
|
||||
mQqsMediaHost.setSquishFraction(mSquishinessFraction);
|
||||
updateMediaPositions();
|
||||
}
|
||||
|
||||
|
||||
@@ -1332,12 +1332,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
*/
|
||||
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
|
||||
public void setExpandedHeight(float height) {
|
||||
final float shadeBottom = getHeight() - getEmptyBottomMargin();
|
||||
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
|
||||
if (!skipHeightUpdate) {
|
||||
final float expansionFraction = MathUtils.saturate(height / shadeBottom);
|
||||
mAmbientState.setExpansionFraction(expansionFraction);
|
||||
}
|
||||
updateStackPosition();
|
||||
|
||||
if (!skipHeightUpdate) {
|
||||
|
||||
@@ -3009,9 +3009,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
}
|
||||
|
||||
private int calculatePanelHeightShade() {
|
||||
int emptyBottomMargin = mNotificationStackScrollLayoutController.getEmptyBottomMargin();
|
||||
int maxHeight = mNotificationStackScrollLayoutController.getHeight() - emptyBottomMargin;
|
||||
|
||||
final int maxHeight = mNotificationStackScrollLayoutController.getHeight();
|
||||
if (mBarState == KEYGUARD) {
|
||||
int minKeyguardPanelBottom = mClockPositionAlgorithm.getLockscreenStatusViewHeight()
|
||||
+ mNotificationStackScrollLayoutController.getIntrinsicContentHeight();
|
||||
|
||||
@@ -796,6 +796,7 @@ public abstract class PanelViewController {
|
||||
}
|
||||
mExpandedFraction = Math.min(1f,
|
||||
maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
|
||||
mAmbientState.setExpansionFraction(mExpandedFraction);
|
||||
onHeightUpdated(mExpandedHeight);
|
||||
updatePanelExpansionAndVisibility();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.android.systemui.media
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.animation.MeasurementInput
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
import com.android.systemui.util.animation.TransitionViewState
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Tests for {@link MediaViewController}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class MediaViewControllerTest : SysuiTestCase() {
|
||||
private val configurationController =
|
||||
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
|
||||
private val mediaHostStatesManager = MediaHostStatesManager()
|
||||
private val mediaViewController =
|
||||
MediaViewController(context, configurationController, mediaHostStatesManager)
|
||||
private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
|
||||
private var transitionLayout = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mediaViewController.attach(transitionLayout, MediaViewController.TYPE.PLAYER)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testObtainViewState_applySquishFraction_toTransitionViewState_height() {
|
||||
transitionLayout.measureState = TransitionViewState().apply {
|
||||
this.height = 100
|
||||
}
|
||||
mediaHostStateHolder.expansion = 1f
|
||||
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||
mediaHostStateHolder.measurementInput =
|
||||
MeasurementInput(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
// Test no squish
|
||||
mediaHostStateHolder.squishFraction = 1f
|
||||
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
|
||||
|
||||
// Test half squish
|
||||
mediaHostStateHolder.squishFraction = 0.5f
|
||||
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user