From b2b25cb104782eb42f5f453fc348d4b61529a421 Mon Sep 17 00:00:00 2001 From: Anton Potapov Date: Thu, 13 Apr 2023 17:17:18 +0100 Subject: [PATCH] Fix split_shade_status_bar height to avoid layout on each animation frame This happens because MotionLayout with height==WRAP_CONTENT updates it's height when transitioning from one state to another. Given the fact that below content assumes the final height of the split_shade_status_bar being around 120dp brightness slider accordingly Test: manual on foldable. Verified screenshots before and after Test: atest NotificationsQSContainerControllerTest Fixes: 267125811 Change-Id: I1332f4c0115366afdafdd0eb17e01153216d253c --- .../res/layout/combined_qs_header.xml | 15 ++- packages/SystemUI/res/values/dimens.xml | 1 + packages/SystemUI/res/xml/qqs_header.xml | 8 +- .../NotificationsQSContainerController.kt | 6 +- .../NotificationsQSContainerControllerTest.kt | 109 ++++++++++++++++++ 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index 441f963a855ae..386c9d66a0c1b 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -14,12 +14,17 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> + + + + 6dp 4dp 20dp + 120dp 48dp 24dp diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index 00a0444a1c9d7..1950965fc298a 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -28,7 +28,7 @@ android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintStart_toStartOf="@id/begin_guide" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline" app:layout_constraintEnd_toStartOf="@id/date" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="packed" @@ -62,7 +62,7 @@ app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline" app:layout_constraintHorizontal_bias="1" app:layout_constraintHorizontal_chainStyle="packed" /> @@ -77,7 +77,7 @@ app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="@id/end_guide" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline" app:layout_constraintHorizontal_bias="1" app:layout_constraintHorizontal_chainStyle="packed" /> @@ -105,7 +105,7 @@ app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toEndOf="@id/end_guide" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline" app:layout_constraintHorizontal_bias="1" /> diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index fb7c5c2e31faa..31b361f837585 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -18,7 +18,6 @@ package com.android.systemui.shade import android.view.View import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowInsets import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet @@ -62,6 +61,7 @@ class NotificationsQSContainerController @Inject constructor( private var isQSCustomizing = false private var isQSCustomizerAnimating = false + private var shadeHeaderHeight = 0 private var largeScreenShadeHeaderHeight = 0 private var largeScreenShadeHeaderActive = false private var notificationsBottomMargin = 0 @@ -146,6 +146,8 @@ class NotificationsQSContainerController @Inject constructor( R.dimen.notification_panel_margin_bottom) largeScreenShadeHeaderHeight = resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height) + shadeHeaderHeight = + resources.getDimensionPixelSize(R.dimen.qs_header_height) panelMarginHorizontal = resources.getDimensionPixelSize( R.dimen.notification_panel_margin_horizontal) topMargin = if (largeScreenShadeHeaderActive) { @@ -245,7 +247,7 @@ class NotificationsQSContainerController @Inject constructor( if (largeScreenShadeHeaderActive) { constraintSet.constrainHeight(R.id.split_shade_status_bar, largeScreenShadeHeaderHeight) } else { - constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT) + constraintSet.constrainHeight(R.id.split_shade_status_bar, shadeHeaderHeight) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt new file mode 100644 index 0000000000000..d4751c86a87f7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 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.shade + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableResources +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.fragments.FragmentService +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +@SmallTest +class NotificationsQSContainerControllerTest : SysuiTestCase() { + + @Mock lateinit var view: NotificationsQuickSettingsContainer + @Mock lateinit var navigationModeController: NavigationModeController + @Mock lateinit var overviewProxyService: OverviewProxyService + @Mock lateinit var shadeHeaderController: ShadeHeaderController + @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var fragmentService: FragmentService + + lateinit var underTest: NotificationsQSContainerController + + private lateinit var fakeResources: TestableResources + + private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock()) + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + fakeResources = TestableResources(context.resources) + + whenever(view.resources).thenReturn(fakeResources.resources) + + underTest = + NotificationsQSContainerController( + view, + navigationModeController, + overviewProxyService, + shadeHeaderController, + shadeExpansionStateManager, + fragmentService, + delayableExecutor, + ) + } + + @Test + fun testSmallScreen_updateResources_splitShadeHeightIsSet() { + with(fakeResources) { + addOverride(R.bool.config_use_large_screen_shade_header, false) + addOverride(R.dimen.qs_header_height, 1) + addOverride(R.dimen.large_screen_shade_header_height, 2) + } + + underTest.updateResources() + + val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) + verify(view).applyConstraints(capture(captor)) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1) + } + + @Test + fun testLargeScreen_updateResources_splitShadeHeightIsSet() { + with(fakeResources) { + addOverride(R.bool.config_use_large_screen_shade_header, true) + addOverride(R.dimen.qs_header_height, 1) + addOverride(R.dimen.large_screen_shade_header_height, 2) + } + + underTest.updateResources() + + val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) + verify(view).applyConstraints(capture(captor)) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2) + } +}