diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index d472aeee10731..d978d02d63f19 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -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(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 } } @@ -879,8 +894,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 @@ -889,6 +910,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, @@ -1113,6 +1138,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, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index eb209f723cb51..d08b6f825f41e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 27da6f3d2d2ce..842a1b92f6909 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index f8680552f90a0..ceb895f74d90c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index 7d61991c910a6..61da18224023b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -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 { 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 { @Override protected void onViewAttached() { - mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); + mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter); mConfigurationController.addCallback(mConfigurationListener); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index fe8c309ad2f3f..4e631046063bf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -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() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index b04d752738318..11a36ada4e36d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -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) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt new file mode 100644 index 0000000000000..bf82e90e88f67 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt @@ -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(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)) + ) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index 5213a30cfd599..04bbd60b8d90b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -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 } }