Clip UMO in overlay and animate FooterActions
The footer actions fade in at the end of the expansion instead of after. Fixes: 224976875 Test: manual Change-Id: I8231b83dda77d2e35655eb7f460c61a9e0ed3f29
This commit is contained in:
@@ -98,6 +98,10 @@ class MediaHierarchyManager @Inject constructor(
|
||||
private var currentBounds = Rect()
|
||||
private var animationStartBounds: Rect = Rect()
|
||||
|
||||
private var animationStartClipping = Rect()
|
||||
private var currentClipping = Rect()
|
||||
private var targetClipping = Rect()
|
||||
|
||||
/**
|
||||
* The cross fade progress at the start of the animation. 0.5f means it's just switching between
|
||||
* the start and the end location and the content is fully faded, while 0.75f means that we're
|
||||
@@ -144,7 +148,8 @@ class MediaHierarchyManager @Inject constructor(
|
||||
}
|
||||
interpolateBounds(animationStartBounds, targetBounds, boundsProgress,
|
||||
result = currentBounds)
|
||||
applyState(currentBounds, currentAlpha)
|
||||
resolveClipping(currentClipping)
|
||||
applyState(currentBounds, currentAlpha, clipBounds = currentClipping)
|
||||
}
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
private var cancelled: Boolean = false
|
||||
@@ -169,6 +174,12 @@ class MediaHierarchyManager @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
private fun resolveClipping(result: Rect) {
|
||||
if (animationStartClipping.isEmpty) result.set(targetClipping)
|
||||
else if (targetClipping.isEmpty) result.set(animationStartClipping)
|
||||
else result.setIntersect(animationStartClipping, targetClipping)
|
||||
}
|
||||
|
||||
private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
|
||||
/**
|
||||
* The last location where this view was at before going to the desired location. This is
|
||||
@@ -629,10 +640,12 @@ class MediaHierarchyManager @Inject constructor(
|
||||
// We also go in here in case the view was detached, since the bounds wouldn't
|
||||
// be correct anymore
|
||||
animationStartBounds.set(currentBounds)
|
||||
animationStartClipping.set(currentClipping)
|
||||
} else {
|
||||
// otherwise, let's take the freshest state, since the current one could
|
||||
// be outdated
|
||||
animationStartBounds.set(previousHost.currentBounds)
|
||||
animationStartClipping.set(previousHost.currentClipping)
|
||||
}
|
||||
val transformationType = calculateTransformationType()
|
||||
var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
|
||||
@@ -745,7 +758,7 @@ class MediaHierarchyManager @Inject constructor(
|
||||
// Let's immediately apply the target state (which is interpolated) if there is
|
||||
// no animation running. Otherwise the animation update will already update
|
||||
// the location
|
||||
applyState(targetBounds, carouselAlpha)
|
||||
applyState(targetBounds, carouselAlpha, clipBounds = targetClipping)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -769,9 +782,11 @@ class MediaHierarchyManager @Inject constructor(
|
||||
val newBounds = endHost.currentBounds
|
||||
val previousBounds = starthost.currentBounds
|
||||
targetBounds = interpolateBounds(previousBounds, newBounds, progress)
|
||||
targetClipping = endHost.currentClipping
|
||||
} else if (endHost != null) {
|
||||
val bounds = endHost.currentBounds
|
||||
targetBounds.set(bounds)
|
||||
targetClipping = endHost.currentClipping
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,8 +889,14 @@ class MediaHierarchyManager @Inject constructor(
|
||||
/**
|
||||
* Apply the current state to the view, updating it's bounds and desired state
|
||||
*/
|
||||
private fun applyState(bounds: Rect, alpha: Float, immediately: Boolean = false) {
|
||||
private fun applyState(
|
||||
bounds: Rect,
|
||||
alpha: Float,
|
||||
immediately: Boolean = false,
|
||||
clipBounds: Rect = EMPTY_RECT
|
||||
) {
|
||||
currentBounds.set(bounds)
|
||||
currentClipping = clipBounds
|
||||
carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
|
||||
val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
|
||||
val startLocation = if (onlyUseEndState) -1 else previousLocation
|
||||
@@ -884,6 +905,10 @@ class MediaHierarchyManager @Inject constructor(
|
||||
mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
|
||||
updateHostAttachment()
|
||||
if (currentAttachmentLocation == IN_OVERLAY) {
|
||||
// Setting the clipping on the hierarchy of `mediaFrame` does not work
|
||||
if (!currentClipping.isEmpty) {
|
||||
currentBounds.intersect(currentClipping)
|
||||
}
|
||||
mediaFrame.setLeftTopRightBottom(
|
||||
currentBounds.left,
|
||||
currentBounds.top,
|
||||
@@ -1108,6 +1133,7 @@ class MediaHierarchyManager @Inject constructor(
|
||||
const val TRANSFORMATION_TYPE_FADE = 1
|
||||
}
|
||||
}
|
||||
private val EMPTY_RECT = Rect()
|
||||
|
||||
@IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [
|
||||
MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
|
||||
|
||||
@@ -55,6 +55,13 @@ class MediaHost constructor(
|
||||
return field
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clipping that this host should use, based on its parent's bounds.
|
||||
*
|
||||
* Use [Rect.set].
|
||||
*/
|
||||
val currentClipping = Rect()
|
||||
|
||||
private val listener = object : MediaDataManager.Listener {
|
||||
override fun onMediaDataLoaded(
|
||||
key: String,
|
||||
|
||||
@@ -247,18 +247,7 @@ internal class FooterActionsController @Inject constructor(
|
||||
}
|
||||
|
||||
fun setExpansion(headerExpansionFraction: Float) {
|
||||
if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
|
||||
if (headerExpansionFraction != lastExpansion) {
|
||||
if (headerExpansionFraction >= 1f) {
|
||||
mView.animate().alpha(1f).setDuration(500L).start()
|
||||
} else if (lastExpansion >= 1f && headerExpansionFraction < 1f) {
|
||||
mView.animate().alpha(0f).setDuration(250L).start()
|
||||
}
|
||||
lastExpansion = headerExpansionFraction
|
||||
}
|
||||
} else {
|
||||
alphaAnimator.setPosition(headerExpansionFraction)
|
||||
}
|
||||
alphaAnimator.setPosition(headerExpansionFraction)
|
||||
}
|
||||
|
||||
fun setKeyguardShowing(showing: Boolean) {
|
||||
|
||||
@@ -139,13 +139,17 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
|
||||
}
|
||||
|
||||
void updateResources(QSPanelController qsPanelController,
|
||||
QuickStatusBarHeaderController quickStatusBarHeaderController) {
|
||||
QuickStatusBarHeaderController quickStatusBarHeaderController,
|
||||
boolean newFooter) {
|
||||
int bottomPadding = 0;
|
||||
if (newFooter) {
|
||||
bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
|
||||
}
|
||||
mQSPanelContainer.setPaddingRelative(
|
||||
mQSPanelContainer.getPaddingStart(),
|
||||
Utils.getQsHeaderSystemIconsAreaHeight(mContext),
|
||||
mQSPanelContainer.getPaddingEnd(),
|
||||
mQSPanelContainer.getPaddingBottom()
|
||||
);
|
||||
bottomPadding);
|
||||
|
||||
int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
|
||||
int padding = getResources().getDimensionPixelSize(
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.systemui.qs;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.qs.dagger.QSScope;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.util.ViewController;
|
||||
@@ -30,23 +32,26 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
|
||||
private final QSPanelController mQsPanelController;
|
||||
private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
|
||||
private final ConfigurationController mConfigurationController;
|
||||
private final boolean mNewFooter;
|
||||
|
||||
private final ConfigurationController.ConfigurationListener mConfigurationListener =
|
||||
new ConfigurationController.ConfigurationListener() {
|
||||
@Override
|
||||
public void onConfigChanged(Configuration newConfig) {
|
||||
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
|
||||
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
|
||||
}
|
||||
};
|
||||
|
||||
@Inject
|
||||
QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController,
|
||||
QuickStatusBarHeaderController quickStatusBarHeaderController,
|
||||
ConfigurationController configurationController) {
|
||||
ConfigurationController configurationController,
|
||||
FeatureFlags featureFlags) {
|
||||
super(view);
|
||||
mQsPanelController = qsPanelController;
|
||||
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
|
||||
mConfigurationController = configurationController;
|
||||
mNewFooter = featureFlags.isEnabled(Flags.NEW_FOOTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,7 +65,7 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
|
||||
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
|
||||
mConfigurationController.addCallback(mConfigurationListener);
|
||||
}
|
||||
|
||||
|
||||
@@ -599,6 +599,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
mQSPanelScrollView.getHeight());
|
||||
}
|
||||
mQSPanelScrollView.setClipBounds(mQsBounds);
|
||||
|
||||
mQsMediaHost.getCurrentClipping().set(0, 0, getView().getMeasuredWidth(),
|
||||
mQSPanelScrollView.getMeasuredHeight() - mQSPanelScrollView.getPaddingBottom());
|
||||
}
|
||||
|
||||
private void updateMediaPositions() {
|
||||
|
||||
@@ -374,7 +374,7 @@ public class QSPanel extends LinearLayout implements Tunable {
|
||||
setPaddingRelative(getPaddingStart(),
|
||||
paddingTop,
|
||||
getPaddingEnd(),
|
||||
mUseNewFooter ? res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom) : 0);
|
||||
getPaddingEnd());
|
||||
}
|
||||
|
||||
void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.android.systemui.qs
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.qs.customize.QSCustomizer
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
class QSContainerImplTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var quickStatusBarHeader: QuickStatusBarHeader
|
||||
@Mock
|
||||
private lateinit var qsCustomizer: QSCustomizer
|
||||
@Mock
|
||||
private lateinit var qsPanelContainer: NonInterceptingScrollView
|
||||
@Mock
|
||||
private lateinit var qsPanelController: QSPanelController
|
||||
@Mock
|
||||
private lateinit var quickStatusBarHeaderController: QuickStatusBarHeaderController
|
||||
|
||||
private lateinit var qsContainer: QSContainerImpl
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
qsContainer = QSContainerImpl(mContext, null)
|
||||
|
||||
setUpMockView(quickStatusBarHeader, R.id.header)
|
||||
setUpMockView(qsCustomizer, R.id.qs_customize)
|
||||
setUpMockView(qsPanelContainer, R.id.expanded_qs_scroll_view)
|
||||
|
||||
qsContainer.onFinishInflate()
|
||||
}
|
||||
|
||||
private fun setUpMockView(view: View, id: Int) {
|
||||
whenever(view.findViewById<View>(id)).thenReturn(view)
|
||||
whenever(view.layoutParams).thenReturn(FrameLayout.LayoutParams(0, 0))
|
||||
qsContainer.addView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testContainerBottomPadding() {
|
||||
qsContainer.updateResources(
|
||||
qsPanelController,
|
||||
quickStatusBarHeaderController,
|
||||
/* newFooter */ false
|
||||
)
|
||||
verify(qsPanelContainer).setPaddingRelative(anyInt(), anyInt(), anyInt(), eq(0))
|
||||
|
||||
qsContainer.updateResources(
|
||||
qsPanelController,
|
||||
quickStatusBarHeaderController,
|
||||
/* newFooter */ true
|
||||
)
|
||||
verify(qsPanelContainer)
|
||||
.setPaddingRelative(
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
eq(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -137,20 +137,6 @@ class QSPanelTest : SysuiTestCase() {
|
||||
assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBottomPadding() {
|
||||
mQsPanel.setUseNewFooter(false)
|
||||
|
||||
mQsPanel.updatePadding()
|
||||
assertThat(mQsPanel.paddingBottom).isEqualTo(0)
|
||||
|
||||
mQsPanel.setUseNewFooter(true)
|
||||
|
||||
mQsPanel.updatePadding()
|
||||
assertThat(mQsPanel.paddingBottom)
|
||||
.isEqualTo(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
|
||||
}
|
||||
|
||||
private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
|
||||
context.resources.configuration.apply { orientation = newOrientation }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user