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
This commit is contained in:
Anton Potapov
2023-04-13 17:17:18 +01:00
parent fccfd4de30
commit b2b25cb104
5 changed files with 132 additions and 7 deletions

View File

@@ -14,12 +14,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
keep split_shade_status_bar height constant to avoid requestLayout calls on each
frame when animating QS <-> QQS transition
-->
<com.android.systemui.util.NoRemeasureMotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/split_shade_status_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/qs_header_height"
android:minHeight="@dimen/large_screen_shade_header_min_height"
android:clickable="false"
android:focusable="true"
@@ -29,6 +34,14 @@
android:theme="@style/Theme.SystemUI.QuickSettings.Header"
app:layoutDescription="@xml/combined_qs_header_scene">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/qqs_header_bottom_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="@dimen/large_screen_shade_header_min_height"
/>
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -615,6 +615,7 @@
<dimen name="qs_header_carrier_separator_width">6dp</dimen>
<dimen name="qs_carrier_margin_width">4dp</dimen>
<dimen name="qs_footer_icon_size">20dp</dimen>
<dimen name="qs_header_height">120dp</dimen>
<dimen name="qs_header_row_min_height">48dp</dimen>
<dimen name="qs_header_non_clickable_element_height">24dp</dimen>

View File

@@ -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"
/>
</Constraint>

View File

@@ -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)
}
}

View File

@@ -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)
}
}