Fix QS in split shade
This CL fixes the following for split shade:
* Tiles not responding when in Split shade: pass the correct split shade
state (from ShadeInteractor).
* Pages peeking: also split shade correct state
* Move to a second page, close and reopen, it should go back to first
page: when closing, make sure to set the state to CLOSED, so it will
correctly revert to the first page. Because of the internals of the
ViewPager, this must be done when the view is attached.
* Leaving scene while in QS mode: fixed with previous fix.
* Animation for entering/exiting QS in split shade. Add a fade that
matches the header, as well as squishiness.
Test: manual
Test: atest QSSceneAdapterTest QSSceneAdapterImplTest
Bug: 329662922
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: I3a11d9749e68a5ae1c1002bb9728dc555e259e2a
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index bc48dd1..244861c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -36,7 +37,8 @@
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
-import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Unsquishing
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.UnsquishingQQS
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.UnsquishingQS
import com.android.systemui.scene.shared.model.Scenes
object QuickSettings {
@@ -49,6 +51,8 @@
object Elements {
val Content =
ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
+ val QuickQuickSettings = ElementKey("QuickQuickSettings")
+ val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings")
val FooterActions = ElementKey("QuickSettingsFooterActions")
}
@@ -78,12 +82,16 @@
is TransitionState.Transition ->
with(transitionState) {
when {
- isSplitShade -> QSSceneAdapter.State.QS
- fromScene == Scenes.Shade && toScene == Scenes.QuickSettings ->
+ isSplitShade -> UnsquishingQS(squishiness)
+ fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> {
Expanding(progress)
- fromScene == Scenes.QuickSettings && toScene == Scenes.Shade ->
+ }
+ fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> {
Collapsing(progress)
- fromScene == Scenes.Shade || toScene == Scenes.Shade -> Unsquishing(squishiness)
+ }
+ fromScene == Scenes.Shade || toScene == Scenes.Shade -> {
+ UnsquishingQQS(squishiness)
+ }
fromScene == Scenes.QuickSettings || toScene == Scenes.QuickSettings -> {
QSSceneAdapter.State.QS
}
@@ -119,6 +127,18 @@
squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
) {
val contentState = stateForQuickSettingsContent(isSplitShade, squishiness)
+ val transitionState = layoutState.transitionState
+ val isClosing =
+ transitionState is TransitionState.Transition &&
+ transitionState.progress >= 0.9f && // almost done closing
+ !(layoutState.isTransitioning(to = Scenes.Shade) ||
+ layoutState.isTransitioning(to = Scenes.QuickSettings))
+
+ if (isClosing) {
+ DisposableEffect(Unit) {
+ onDispose { qsSceneAdapter.setState(QSSceneAdapter.State.CLOSED) }
+ }
+ }
MovableElement(
key = QuickSettings.Elements.Content,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 5c6e1c8..9b59708 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -13,11 +13,18 @@
) {
spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.Clock) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.PrivacyChip) }
- translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+ fractionRange(start = .58f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ }
+ translate(
+ QuickSettings.Elements.QuickQuickSettings,
+ y = -ShadeHeader.Dimensions.CollapsedHeight * .66f
+ )
translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 31201c2f..e1b56f1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -222,15 +222,17 @@
horizontal = Shade.Dimensions.HorizontalPadding
)
)
- QuickSettings(
- viewModel.qsSceneAdapter,
- {
- (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
- .roundToInt()
- },
- isSplitShade = false,
- squishiness = tileSquishiness,
- )
+ Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
+ QuickSettings(
+ viewModel.qsSceneAdapter,
+ {
+ (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
+ .roundToInt()
+ },
+ isSplitShade = false,
+ squishiness = tileSquishiness,
+ )
+ }
MediaIfVisible(
viewModel = viewModel,
@@ -280,6 +282,8 @@
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
+ val tileSquishiness by
+ animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val density = LocalDensity.current
@@ -325,12 +329,17 @@
.padding(bottom = navBarBottomHeight)
}
) {
- QuickSettings(
- qsSceneAdapter = viewModel.qsSceneAdapter,
- heightProvider = { viewModel.qsSceneAdapter.qsHeight },
- isSplitShade = true,
- modifier = Modifier.fillMaxWidth(),
- )
+ Box(
+ modifier = Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ ) {
+ QuickSettings(
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = true,
+ modifier = Modifier.fillMaxWidth(),
+ squishiness = tileSquishiness,
+ )
+ }
MediaIfVisible(
viewModel = viewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 3c0ab24..27c4ec1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -27,9 +27,17 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -41,8 +49,6 @@
import java.util.Locale
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -57,8 +63,9 @@
@OptIn(ExperimentalCoroutinesApi::class)
class QSSceneAdapterImplTest : SysuiTestCase() {
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val kosmos = Kosmos().apply { testCase = this@QSSceneAdapterImplTest }
+ private val testDispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
private val qsImplProvider =
object : Provider<QSImpl> {
@@ -107,10 +114,15 @@
}
}
+ private val shadeInteractor = kosmos.shadeInteractor
+ private val dumpManager = mock<DumpManager>()
+
private val underTest =
QSSceneAdapterImpl(
qsSceneComponentFactory,
qsImplProvider,
+ shadeInteractor,
+ dumpManager,
testDispatcher,
testScope.backgroundScope,
configurationInteractor,
@@ -158,12 +170,6 @@
)
verify(this).setListening(false)
verify(this).setExpanded(false)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
}
}
@@ -187,13 +193,7 @@
/* squishinessFraction= */ 1f,
)
verify(this).setListening(true)
- verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
+ verify(this).setExpanded(false)
}
}
@@ -218,12 +218,6 @@
)
verify(this).setListening(true)
verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
}
}
@@ -249,12 +243,6 @@
)
verify(this).setListening(true)
verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
}
}
@@ -268,7 +256,7 @@
runCurrent()
clearInvocations(qsImpl!!)
- underTest.setState(QSSceneAdapter.State.Unsquishing(squishiness))
+ underTest.setState(QSSceneAdapter.State.UnsquishingQQS(squishiness))
with(qsImpl!!) {
verify(this).setQsVisible(true)
verify(this)
@@ -279,13 +267,7 @@
/* squishinessFraction= */ squishiness,
)
verify(this).setListening(true)
- verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ squishiness,
- )
+ verify(this).setExpanded(false)
}
}
@@ -497,4 +479,21 @@
verify(qsImpl!!).applyBottomNavBarToCustomizerPadding(navBarHeight)
}
+
+ @Test
+ fun dispatchSplitShade() =
+ testScope.runTest {
+ val shadeRepository = kosmos.fakeShadeRepository
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ verify(qsImpl!!).setInSplitShade(false)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ runCurrent()
+ verify(qsImpl!!).setInSplitShade(true)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index e281383..ebd65fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -49,9 +49,16 @@
}
@Test
- fun unsquishing_expansionSameAsQQS() {
+ fun unsquishingQQS_expansionSameAsQQS() {
val squishiness = 0.6f
- assertThat(QSSceneAdapter.State.Unsquishing(squishiness).expansion)
+ assertThat(QSSceneAdapter.State.UnsquishingQQS(squishiness).expansion)
.isEqualTo(QSSceneAdapter.State.QQS.expansion)
}
+
+ @Test
+ fun unsquishingQS_expansionSameAsQS() {
+ val squishiness = 0.6f
+ assertThat(QSSceneAdapter.State.UnsquishingQS(squishiness).expansion)
+ .isEqualTo(QSSceneAdapter.State.QS.expansion)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index dc487af..00757b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -501,8 +501,10 @@
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ if (!mExpanded && mTileLayout instanceof PagedTileLayout tilesLayout) {
+ // Use post, so it will wait until the view is attached. If the view is not attached,
+ // it will not populate corresponding views (and will not do it later when attached).
+ tilesLayout.post(() -> tilesLayout.setCurrentItem(0, false));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index c1b2037..6710504 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -23,16 +23,21 @@
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import com.android.settingslib.applications.InterestingConfigChanges
+import com.android.systemui.Dumpable
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.QSContainerImpl
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.sample
+import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
import kotlin.coroutines.resume
@@ -107,11 +112,17 @@
}
/** State for appearing QQS from Lockscreen or Gone */
- data class Unsquishing(override val squishiness: Float) : State {
+ data class UnsquishingQQS(override val squishiness: Float) : State {
override val isVisible = true
override val expansion = 0f
}
+ /** State for appearing QS from Lockscreen or Gone, used in Split shade */
+ data class UnsquishingQS(override val squishiness: Float) : State {
+ override val isVisible = true
+ override val expansion = 1f
+ }
+
companion object {
// These are special cases of the expansion.
val QQS = Expanding(0f)
@@ -129,22 +140,28 @@
constructor(
private val qsSceneComponentFactory: QSSceneComponent.Factory,
private val qsImplProvider: Provider<QSImpl>,
+ shadeInteractor: ShadeInteractor,
+ dumpManager: DumpManager,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application applicationScope: CoroutineScope,
private val configurationInteractor: ConfigurationInteractor,
private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
-) : QSContainerController, QSSceneAdapter {
+) : QSContainerController, QSSceneAdapter, Dumpable {
@Inject
constructor(
qsSceneComponentFactory: QSSceneComponent.Factory,
qsImplProvider: Provider<QSImpl>,
+ shadeInteractor: ShadeInteractor,
+ dumpManager: DumpManager,
@Main dispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
configurationInteractor: ConfigurationInteractor,
) : this(
qsSceneComponentFactory,
qsImplProvider,
+ shadeInteractor,
+ dumpManager,
dispatcher,
scope,
configurationInteractor,
@@ -182,6 +199,7 @@
)
init {
+ dumpManager.registerDumpable(this)
applicationScope.launch {
launch {
state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
@@ -210,6 +228,11 @@
it.second.applyBottomNavBarToCustomizerPadding(it.first)
}
}
+ launch {
+ shadeInteractor.shadeMode.collect {
+ qsImpl.value?.setInSplitShade(it == ShadeMode.Split)
+ }
+ }
}
}
@@ -256,9 +279,17 @@
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
- setExpanded(state.isVisible)
+ setExpanded(state.isVisible && state.expansion > 0f)
setListening(state.isVisible)
setQsExpansion(state.expansion, 1f, 0f, state.squishiness)
- setTransitionToFullShadeProgress(false, 1f, state.squishiness)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply {
+ println("Last state: ${state.value}")
+ println("Customizing: ${isCustomizing.value}")
+ println("QQS height: $qqsHeight")
+ println("QS height: $qsHeight")
+ }
}
}