diff options
| author | 2023-10-10 13:28:09 -0400 | |
|---|---|---|
| committer | 2023-11-13 16:12:29 -0500 | |
| commit | cbf3213914891f9f335e034f544ece6713085e3e (patch) | |
| tree | da38df5909dfa7cdee694e645122ab7a397fa2c4 | |
| parent | 8621fe013087876d8e97ba13a44dfdfb4bb5f3b8 (diff) | |
Create a QSSceneInteractor
This interactor bridges between the Scene system and QSImpl.
In this CL, the following is working (behind the corresponding flags):
* Expanding the shade allows access to fully functioning QQS, QS and
edit mode.
* Interacting with QQS, QS and edit mode works as expected.
What is not working yet:
* Transition animation in expansions
* Visual glitches when expanding for the first time.
* QS in lockscreen
* Re-inflation when theme changes.
Test: atest SystemUITests
Test: manual
Bug: 280887232
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: I096db66b3b5a8d56d78ec50448e7faa612f9ce8c
30 files changed, 883 insertions, 134 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt deleted file mode 100644 index c84a5e91ca50..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.qs.footer.ui.compose - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope - -object QuickSettings { - object Elements { - // TODO RENAME - val Content = ElementKey("QuickSettingsContent") - val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid") - val FooterActions = ElementKey("QuickSettingsFooterActions") - } -} - -@Composable -fun SceneScope.QuickSettings( - modifier: Modifier = Modifier, -) { - // TODO(b/272780058): implement. - Column( - modifier = - modifier - .element(QuickSettings.Elements.Content) - .fillMaxWidth() - .defaultMinSize(minHeight = 300.dp) - .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.primary) - .padding(16.dp), - ) { - Text( - text = "Quick settings grid", - modifier = - Modifier.element(QuickSettings.Elements.CollapsedGrid) - .align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "QS footer actions", - modifier = - Modifier.element(QuickSettings.Elements.FooterActions) - .align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onPrimary, - ) - } -} 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 new file mode 100644 index 000000000000..28a4801d582a --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -0,0 +1,130 @@ +/* + * 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.qs.ui.composable + +import android.view.ContextThemeWrapper +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.theme.colorAttr +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.res.R + +object QuickSettings { + object Elements { + // TODO RENAME + val Content = ElementKey("QuickSettingsContent") + val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid") + val FooterActions = ElementKey("QuickSettingsFooterActions") + } +} + +@Composable +private fun QuickSettingsTheme(content: @Composable () -> Unit) { + val context = LocalContext.current + val themedContext = + remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) } + CompositionLocalProvider(LocalContext provides themedContext) { content() } +} + +@Composable +fun SceneScope.QuickSettings( + modifier: Modifier = Modifier, + qsSceneAdapter: QSSceneAdapter, + state: QSSceneAdapter.State +) { + // TODO(b/272780058): implement. + Column( + modifier = + modifier + .element(QuickSettings.Elements.Content) + .fillMaxWidth() + .defaultMinSize(minHeight = 300.dp) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.primary) + .padding(1.dp), + ) { + QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state) + } +} + +@Composable +private fun QuickSettingsContent( + qsSceneAdapter: QSSceneAdapter, + state: QSSceneAdapter.State, + modifier: Modifier = Modifier, +) { + val qsView by qsSceneAdapter.qsView.collectAsState(null) + QuickSettingsTheme { + val context = LocalContext.current + + val frame by remember(context) { mutableStateOf(FrameLayout(context)) } + + LaunchedEffect(key1 = context) { + if (qsView == null) { + qsSceneAdapter.inflate(context, frame) + } + } + qsView?.let { + it.attachToParent(frame) + AndroidView( + modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)), + factory = { _ -> + qsSceneAdapter.setState(state) + frame + }, + onRelease = { frame.removeAllViews() }, + update = { qsSceneAdapter.setState(state) } + ) + } + } +} + +private fun View.attachToParent(parent: ViewGroup) { + if (this.parent != null && this.parent != parent) { + (this.parent as ViewGroup).removeView(this) + } + if (this.parent != parent) { + parent.addView( + this, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index a33eac55ac3e..b9451d1c1585 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -17,43 +17,53 @@ package com.android.systemui.qs.ui.composable import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel -import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader +import com.android.systemui.shade.ui.composable.ShadeHeader import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager import com.android.systemui.statusbar.phone.StatusBarLocation import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */ @SysUISingleton class QuickSettingsScene @Inject constructor( + @Application private val applicationScope: CoroutineScope, private val viewModel: QuickSettingsSceneViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, @@ -61,14 +71,12 @@ constructor( ) : ComposableScene { override val key = SceneKey.QuickSettings - private val _destinationScenes = - MutableStateFlow<Map<UserAction, SceneModel>>( - mapOf( - UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), - ) - ) - .asStateFlow() - override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = _destinationScenes + override val destinationScenes = + viewModel.destinationScenes.stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = emptyMap(), + ) @Composable override fun SceneScope.Content( @@ -93,6 +101,9 @@ private fun SceneScope.QuickSettingsScene( modifier: Modifier = Modifier, ) { // TODO(b/280887232): implement the real UI. + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val collapsedHeaderHeight = + with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = @@ -103,12 +114,27 @@ private fun SceneScope.QuickSettingsScene( ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> - ExpandedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - ) + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(1000), + initialHeight = { collapsedHeaderHeight }, + ) + fadeIn(tween(1000)), + exit = + shrinkVertically( + animationSpec = tween(1000), + targetHeight = { collapsedHeaderHeight }, + shrinkTowards = Alignment.Top, + ) + fadeOut(tween(1000)), + ) { + ExpandedShadeHeader( + viewModel = viewModel.shadeHeaderViewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + ) + } else -> CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, @@ -118,6 +144,10 @@ private fun SceneScope.QuickSettingsScene( ) } Spacer(modifier = Modifier.height(16.dp)) - QuickSettings() + QuickSettings( + modifier = Modifier.fillMaxHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QS + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt index 7ecfb62c4f62..fadbdce80cbf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt @@ -4,7 +4,7 @@ import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.Shade fun TransitionBuilder.lockscreenToShadeTransition() { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt index be85beea6ee0..5616175ed11c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt @@ -4,7 +4,7 @@ import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.ShadeHeader fun TransitionBuilder.shadeToQuickSettingsTransition() { 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 13ebdf9c4a7c..a02f046a18f5 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 @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -37,7 +38,8 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -152,7 +154,11 @@ private fun SceneScope.ShadeScene( statusBarIconController = statusBarIconController, ) Spacer(modifier = Modifier.height(16.dp)) - QuickSettings(modifier = Modifier.height(160.dp)) + QuickSettings( + modifier = Modifier.wrapContentHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QQS + ) Spacer(modifier = Modifier.height(16.dp)) Notifications(modifier = Modifier.weight(1f)) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index b0f54ee78482..064423768cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -28,8 +28,8 @@ import android.view.View; import android.widget.FrameLayout; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.res.R; import com.android.systemui.shade.TouchLogger; import com.android.systemui.util.LargeScreenUtils; @@ -59,6 +59,8 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private boolean mClippingEnabled; private boolean mIsFullWidth; + private boolean mSceneContainerEnabled; + public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); } @@ -72,6 +74,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } + void setSceneContainerEnabled(boolean enabled) { + mSceneContainerEnabled = enabled; + } + @Override public boolean hasOverlappingRendering() { return false; @@ -161,7 +167,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } mQSPanelContainer.setPaddingRelative( mQSPanelContainer.getPaddingStart(), - topPadding, + mSceneContainerEnabled ? 0 : topPadding, mQSPanelContainer.getPaddingEnd(), mQSPanelContainer.getPaddingBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index 73a5faabda3b..7b001c7b72f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -24,6 +24,7 @@ import android.view.View; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; @@ -44,6 +45,7 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); } }; + private final boolean mSceneContainerEnabled; private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() { @Override @@ -64,18 +66,21 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController, ConfigurationController configurationController, - FalsingManager falsingManager) { + FalsingManager falsingManager, + SceneContainerFlags sceneContainerFlags) { super(view); mQsPanelController = qsPanelController; mQuickStatusBarHeaderController = quickStatusBarHeaderController; mConfigurationController = configurationController; mFalsingManager = falsingManager; mQSPanelContainer = mView.getQSPanelContainer(); + mSceneContainerEnabled = sceneContainerFlags.isEnabled(); } @Override public void onInit() { mQuickStatusBarHeaderController.init(); + mView.setSceneContainerEnabled(mSceneContainerEnabled); } public void setListening(boolean listening) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index fab7e952a874..7f91fd2ebb80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -97,6 +97,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private boolean mStackScrollerOverscrolling; private QSAnimator mQSAnimator; + @Nullable private HeightListener mPanelView; private QSSquishinessController mQSSquishinessController; protected QuickStatusBarHeader mHeader; @@ -340,6 +341,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } if (mQSCustomizerController != null) { mQSCustomizerController.setQs(null); + mQSCustomizerController.setContainerController(null); } mScrollListener = null; if (mContainer != null) { @@ -347,6 +349,10 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } mDumpManager.unregisterDumpable(getClass().getSimpleName()); mListeningAndVisibilityLifecycleOwner.destroy(); + ViewGroup parent = ((ViewGroup) getView().getParent()); + if (parent != null) { + parent.removeView(getView()); + } } public void onSaveInstanceState(Bundle outState) { @@ -853,6 +859,10 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQSCustomizerController.hide(); } + public void closeCustomizerImmediately() { + mQSCustomizerController.hide(false); + } + public void notifyCustomizeChanged() { // The customize state changed, so our height changed. mContainer.updateExpansion(); @@ -863,7 +873,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. - mPanelView.onQsHeightChanged(); + if (mPanelView != null) { + mPanelView.onQsHeightChanged(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 3227f75ed067..ddd7d6781c46 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -39,9 +39,9 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.RemeasuringLinearLayout; -import com.android.systemui.res.R; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.res.R; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -110,6 +110,8 @@ public class QSPanel extends LinearLayout implements Tunable { */ private boolean mCanCollapse = true; + private boolean mSceneContainerEnabled; + public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); mUsingMediaPlayer = useQsMediaPlayer(context); @@ -153,6 +155,13 @@ public class QSPanel extends LinearLayout implements Tunable { } } + void setSceneContainerEnabled(boolean enabled) { + mSceneContainerEnabled = enabled; + if (mSceneContainerEnabled) { + updatePadding(); + } + } + protected void setHorizontalContentContainerClipping() { mHorizontalContentContainer.setClipChildren(true); mHorizontalContentContainer.setClipToPadding(false); @@ -375,7 +384,7 @@ public class QSPanel extends LinearLayout implements Tunable { int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top); int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); setPaddingRelative(getPaddingStart(), - paddingTop, + mSceneContainerEnabled ? 0 : paddingTop, getPaddingEnd(), paddingBottom); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 6bbdc54d260d..5eb9620d7334 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -34,6 +34,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -61,6 +62,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private boolean mListening; + private final boolean mSceneContainerEnabled; + private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -82,7 +85,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { BrightnessSliderController.Factory brightnessSliderFactory, FalsingManager falsingManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - SplitShadeStateController splitShadeStateController) { + SplitShadeStateController splitShadeStateController, + SceneContainerFlags sceneContainerFlags) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController); mTunerService = tunerService; @@ -96,6 +100,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController); mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mSceneContainerEnabled = sceneContainerFlags.isEnabled(); } @Override @@ -116,6 +121,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS); mView.updateResources(); + mView.setSceneContainerEnabled(mSceneContainerEnabled); if (mView.isListening()) { refreshAllTiles(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 67c42086364c..c657b55d42d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -40,6 +40,8 @@ public class QuickStatusBarHeader extends FrameLayout { protected QuickQSPanel mHeaderQsPanel; + private boolean mSceneContainerEnabled; + public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); } @@ -52,6 +54,13 @@ public class QuickStatusBarHeader extends FrameLayout { updateResources(); } + void setSceneContainerEnabled(boolean enabled) { + mSceneContainerEnabled = enabled; + if (mSceneContainerEnabled) { + updateResources(); + } + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -82,7 +91,9 @@ public class QuickStatusBarHeader extends FrameLayout { setLayoutParams(lp); MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams(); - if (largeScreenHeaderActive) { + if (mSceneContainerEnabled) { + qqsLP.topMargin = 0; + } else if (largeScreenHeaderActive) { qqsLP.topMargin = mContext.getResources() .getDimensionPixelSize(R.dimen.qqs_layout_margin_top); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index 64960e6ce23e..1d92d782be69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -17,6 +17,7 @@ package com.android.systemui.qs; import com.android.systemui.qs.dagger.QSScope; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -29,17 +30,20 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private final QuickQSPanelController mQuickQSPanelController; private boolean mListening; + private final boolean mSceneContainerEnabled; @Inject QuickStatusBarHeaderController(QuickStatusBarHeader view, - QuickQSPanelController quickQSPanelController + QuickQSPanelController quickQSPanelController, + SceneContainerFlags sceneContainerFlags ) { super(view); mQuickQSPanelController = quickQSPanelController; + mSceneContainerEnabled = sceneContainerFlags.isEnabled(); } - @Override protected void onViewAttached() { + mView.setSceneContainerEnabled(mSceneContainerEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index 024e760e6ed1..c28371ce8aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -244,7 +244,12 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { /** Hice the customizer. */ public void hide() { - final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF; + hide(true); + } + + public void hide(boolean animated) { + final boolean animate = animated + && mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF; if (mView.isShown()) { mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED); mToolbar.dismissPopupMenus(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index bd4c6e1930ba..1aef9206d99f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -32,6 +32,8 @@ import com.android.systemui.qs.external.QSExternalModule; import com.android.systemui.qs.pipeline.dagger.QSPipelineModule; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.di.QSTilesModule; +import com.android.systemui.qs.ui.adapter.QSSceneAdapter; +import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -42,18 +44,19 @@ import com.android.systemui.statusbar.policy.SafetyController; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.util.settings.SecureSettings; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.Multibinds; - import java.util.Map; import javax.inject.Named; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.Multibinds; + /** * Module for QS dependencies */ -@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class}, +@Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class}, includes = { MediaModule.class, QSExternalModule.class, @@ -110,4 +113,7 @@ public interface QSModule { manager.init(); return manager; } + + @Binds + QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt index ba1aa629f8cd..b9423683446c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt @@ -21,12 +21,12 @@ import com.android.systemui.dagger.qualifiers.RootView import dagger.BindsInstance import dagger.Subcomponent -@Subcomponent(modules = [QSFlexiglassModule::class]) +@Subcomponent(modules = [QSSceneModule::class]) @QSScope -interface QSFlexiglassComponent : QSComponent { +interface QSSceneComponent : QSComponent { @Subcomponent.Factory interface Factory { - fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent + fun create(@RootView @BindsInstance rootView: View): QSSceneComponent } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt index 36fac44e0173..446cb62464fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt @@ -24,7 +24,7 @@ import dagger.Provides import javax.inject.Named @Module(includes = [QSScopeModule::class]) -interface QSFlexiglassModule { +interface QSSceneModule { @Module companion object { 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 new file mode 100644 index 000000000000..b4340f5d59d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -0,0 +1,163 @@ +/* + * 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.qs.ui.adapter + +import android.content.Context +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.QSImpl +import com.android.systemui.qs.dagger.QSSceneComponent +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.sample +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Provider +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +// TODO(307945185) Split View concerns into a ViewBinder +/** Adapter to use between Scene system and [QSImpl] */ +interface QSSceneAdapter { + /** Whether [QSImpl] is currently customizing */ + val isCustomizing: StateFlow<Boolean> + + /** + * A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by + * the interactor. + */ + val qsView: Flow<View> + + /** + * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in + * [qsView] + */ + suspend fun inflate(context: Context, parent: ViewGroup? = null) + + /** Set the current state for QS. [state] must not be [State.INITIAL]. */ + fun setState(state: State) + + sealed class State( + val isVisible: Boolean, + val expansion: Float, + ) { + data object CLOSED : State(false, 0f) + data object QQS : State(true, 0f) + data object QS : State(true, 1f) + } +} + +@SysUISingleton +class QSSceneAdapterImpl +@VisibleForTesting +constructor( + private val qsSceneComponentFactory: QSSceneComponent.Factory, + private val qsImplProvider: Provider<QSImpl>, + @Main private val mainDispatcher: CoroutineDispatcher, + @Application applicationScope: CoroutineScope, + private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater, +) : QSContainerController, QSSceneAdapter { + + @Inject + constructor( + qsSceneComponentFactory: QSSceneComponent.Factory, + qsImplProvider: Provider<QSImpl>, + @Main dispatcher: CoroutineDispatcher, + @Application scope: CoroutineScope, + ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater) + + private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED) + private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isCustomizing = _isCustomizing.asStateFlow() + + private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null) + val qsImpl = _qsImpl.asStateFlow() + override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull() + + init { + applicationScope.launch { + state.sample(_isCustomizing, ::Pair).collect { (state, customizing) -> + _qsImpl.value?.apply { + if (state != QSSceneAdapter.State.QS && customizing) { + this@apply.closeCustomizerImmediately() + } + applyState(state) + } + } + } + } + + override fun setCustomizerAnimating(animating: Boolean) {} + + override fun setCustomizerShowing(showing: Boolean) { + _isCustomizing.value = showing + } + + override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) { + setCustomizerShowing(showing) + } + + override fun setDetailShowing(showing: Boolean) {} + + override suspend fun inflate(context: Context, parent: ViewGroup?) { + withContext(mainDispatcher) { + val inflater = asyncLayoutInflaterFactory(context) + val view = suspendCoroutine { continuation -> + inflater.inflate(R.layout.qs_panel, parent) { view, _, _ -> + continuation.resume(view) + } + } + val bundle = Bundle() + _qsImpl.value?.onSaveInstanceState(bundle) + _qsImpl.value?.onDestroy() + val component = qsSceneComponentFactory.create(view) + val qs = qsImplProvider.get() + qs.onCreate(null) + qs.onComponentCreated(component, bundle) + _qsImpl.value = qs + qs.view.setPadding(0, 0, 0, 0) + qs.setContainerController(this@QSSceneAdapterImpl) + qs.applyState(state.value) + } + } + override fun setState(state: QSSceneAdapter.State) { + this.state.value = state + } + + private fun QSImpl.applyState(state: QSSceneAdapter.State) { + setQsVisible(state.isVisible) + setExpanded(state.isVisible) + setListening(state.isVisible) + setQsExpansion(state.expansion, 1f, 0f, 1f) + setTransitionToFullShadeProgress(false, 1f, 1f) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 5993cf104318..3941fd75ebaa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -18,8 +18,14 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.scene.shared.model.Direction +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.map /** Models UI state and handles user input for the quick settings scene. */ @SysUISingleton @@ -28,7 +34,20 @@ class QuickSettingsSceneViewModel constructor( private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, + val qsSceneAdapter: QSSceneAdapter, ) { /** Notifies that some content in quick settings was clicked. */ fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() + + val destinationScenes = + qsSceneAdapter.isCustomizing.map { customizing -> + if (customizing) { + mapOf<UserAction, SceneModel>(UserAction.Back to SceneModel(SceneKey.QuickSettings)) + } else { + mapOf( + UserAction.Back to SceneModel(SceneKey.Shade), + UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index d73fa1460bd4..6a06830426e6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -107,12 +107,12 @@ import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; -import dagger.Lazy; - import java.io.PrintWriter; import javax.inject.Inject; +import dagger.Lazy; + /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ @@ -724,7 +724,9 @@ public class QuickSettingsController implements Dumpable { /** Closes the Qs customizer. */ public void closeQsCustomizer() { - mQs.closeCustomizer(); + if (mQs != null) { + mQs.closeCustomizer(); + } } /** Returns whether touches from the notification panel should be disallowed */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 20b9edee2d70..af880816914f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -19,13 +19,14 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.SceneKey -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject /** Models UI state and handles user input for the shade scene. */ @SysUISingleton @@ -34,6 +35,7 @@ class ShadeSceneViewModel constructor( @Application private val applicationScope: CoroutineScope, private val deviceEntryInteractor: DeviceEntryInteractor, + val qsSceneAdapter: QSSceneAdapter, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 2e5717de1a86..e0b4aa0b62de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -159,6 +159,7 @@ import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.qs.QSPanelController; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -583,6 +584,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final InteractionJankMonitor mJankMonitor; + private final SceneContainerFlags mSceneContainerFlags; + /** * Public constructor for CentralSurfaces. * @@ -696,7 +699,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { AlternateBouncerInteractor alternateBouncerInteractor, UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, - ActivityStarter activityStarter + ActivityStarter activityStarter, + SceneContainerFlags sceneContainerFlags ) { mContext = context; mNotificationsController = notificationsController; @@ -793,6 +797,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mUserTracker = userTracker; mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; + mSceneContainerFlags = sceneContainerFlags; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -1247,7 +1252,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up the quick settings tile panel final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame); - if (container != null) { + if (container != null && !mSceneContainerFlags.isEnabled()) { FragmentHostManager fragmentHostManager = mFragmentService.getFragmentHostManager(container); ExtensionFragmentListener.attachExtensonToFragment( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 5e4c954a0b26..1f7a02962ce2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -16,6 +16,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager @@ -61,6 +62,8 @@ class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var configuration: Configuration @Mock private lateinit var pagedTileLayout: PagedTileLayout + private val sceneContainerFlags = FakeSceneContainerFlags() + private lateinit var controller: QSPanelController private val testableResources: TestableResources = mContext.orCreateTestableResources @@ -96,7 +99,8 @@ class QSPanelControllerTest : SysuiTestCase() { brightnessSliderFactory, falsingManager, statusBarKeyguardViewManager, - ResourcesSplitShadeStateController() + ResourcesSplitShadeStateController(), + sceneContainerFlags, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 555484cc17f8..8acde3637576 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -20,6 +20,7 @@ import android.content.Context import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import org.junit.After import org.junit.Before import org.junit.Test @@ -42,6 +43,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var context: Context + private val sceneContainerFlags = FakeSceneContainerFlags() + private lateinit var controller: QuickStatusBarHeaderController @Before @@ -51,7 +54,11 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(view.isAttachedToWindow).thenReturn(true) `when`(view.context).thenReturn(context) - controller = QuickStatusBarHeaderController(view, quickQSPanelController) + controller = QuickStatusBarHeaderController( + view, + quickQSPanelController, + sceneContainerFlags, + ) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt new file mode 100644 index 000000000000..c1c012613fa1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -0,0 +1,294 @@ +/* + * 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.qs.ui.adapter + +import android.os.Bundle +import android.view.View +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.QSImpl +import com.android.systemui.qs.dagger.QSComponent +import com.android.systemui.qs.dagger.QSSceneComponent +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +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 +import org.junit.runner.RunWith +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSSceneAdapterImplTest : SysuiTestCase() { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val qsImplProvider = + object : Provider<QSImpl> { + val impls = mutableListOf<QSImpl>() + + override fun get(): QSImpl { + return mock<QSImpl> { + lateinit var _view: View + whenever(onComponentCreated(any(), any())).then { + _view = it.getArgument<QSComponent>(0).getRootView() + Unit + } + whenever(view).thenAnswer { _view } + } + .also { impls.add(it) } + } + } + + private val qsSceneComponentFactory = + object : QSSceneComponent.Factory { + val components = mutableListOf<QSSceneComponent>() + + override fun create(rootView: View): QSSceneComponent { + return mock<QSSceneComponent> { whenever(this.getRootView()).thenReturn(rootView) } + .also { components.add(it) } + } + } + + private val mockAsyncLayoutInflater = + mock<AsyncLayoutInflater>() { + whenever(inflate(anyInt(), nullable(), any())).then { invocation -> + val mockView = mock<View>() + invocation + .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2) + .onInflateFinished( + mockView, + invocation.getArgument(0), + invocation.getArgument(1), + ) + } + } + + private val underTest = + QSSceneAdapterImpl( + qsSceneComponentFactory, + qsImplProvider, + testDispatcher, + testScope.backgroundScope, + { mockAsyncLayoutInflater }, + ) + + @Test + fun inflate() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + assertThat(qsImpl).isNull() + + underTest.inflate(context) + runCurrent() + + assertThat(qsImpl).isNotNull() + assertThat(qsImpl).isSameInstanceAs(qsImplProvider.impls[0]) + val inOrder = inOrder(qsImpl!!) + inOrder.verify(qsImpl!!).onCreate(nullable()) + inOrder + .verify(qsImpl!!) + .onComponentCreated( + eq(qsSceneComponentFactory.components[0]), + any(), + ) + } + + @Test + fun initialState_closed() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + with(qsImpl!!) { + verify(this).setQsVisible(false) + verify(this) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) + verify(this).setListening(false) + verify(this).setExpanded(false) + verify(this) + .setTransitionToFullShadeProgress( + /* isTransitioningToFullShade= */ false, + /* qsTransitionFraction= */ 1f, + /* qsSquishinessFraction = */ 1f, + ) + } + } + + @Test + fun state_qqs() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + clearInvocations(qsImpl!!) + + underTest.setState(QSSceneAdapter.State.QQS) + with(qsImpl!!) { + verify(this).setQsVisible(true) + verify(this) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) + verify(this).setListening(true) + verify(this).setExpanded(true) + verify(this) + .setTransitionToFullShadeProgress( + /* isTransitioningToFullShade= */ false, + /* qsTransitionFraction= */ 1f, + /* qsSquishinessFraction = */ 1f, + ) + } + } + + @Test + fun state_qs() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + clearInvocations(qsImpl!!) + + underTest.setState(QSSceneAdapter.State.QS) + with(qsImpl!!) { + verify(this).setQsVisible(true) + verify(this) + .setQsExpansion( + /* expansion= */ 1f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) + verify(this).setListening(true) + verify(this).setExpanded(true) + verify(this) + .setTransitionToFullShadeProgress( + /* isTransitioningToFullShade= */ false, + /* qsTransitionFraction= */ 1f, + /* qsSquishinessFraction = */ 1f, + ) + } + } + + @Test + fun customizing_QS() = + testScope.runTest { + val customizing by collectLastValue(underTest.isCustomizing) + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + + assertThat(customizing).isFalse() + + underTest.setCustomizerShowing(true) + assertThat(customizing).isTrue() + + underTest.setCustomizerShowing(false) + assertThat(customizing).isFalse() + } + + @Test + fun customizing_moveToQQS_stopCustomizing() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + underTest.setCustomizerShowing(true) + + underTest.setState(QSSceneAdapter.State.QQS) + runCurrent() + verify(qsImpl!!).closeCustomizerImmediately() + } + + @Test + fun customizing_moveToClosed_stopCustomizing() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + underTest.setCustomizerShowing(true) + runCurrent() + + underTest.setState(QSSceneAdapter.State.CLOSED) + verify(qsImpl!!).closeCustomizerImmediately() + } + + @Test + fun reinflation_previousStateDestroyed() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + val oldQsImpl = qsImpl!! + + underTest.inflate(context) + runCurrent() + val newQSImpl = qsImpl!! + + assertThat(oldQsImpl).isNotSameInstanceAs(newQSImpl) + val inOrder = inOrder(oldQsImpl, newQSImpl) + val bundleArgCaptor = argumentCaptor<Bundle>() + + inOrder.verify(oldQsImpl).onSaveInstanceState(capture(bundleArgCaptor)) + inOrder.verify(oldQsImpl).onDestroy() + assertThat(newQSImpl).isSameInstanceAs(qsImplProvider.impls[1]) + inOrder.verify(newQSImpl).onCreate(nullable()) + inOrder + .verify(newQSImpl) + .onComponentCreated( + qsSceneComponentFactory.components[1], + bundleArgCaptor.value, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index d582b9e8da5a..ef129c85ed37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -23,9 +23,12 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor @@ -53,6 +56,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val sceneInteractor = utils.sceneInteractor() private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } + private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() } private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -96,6 +100,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, ), shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsFlexiglassAdapter, ) } @@ -124,4 +129,33 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } + + @Test + fun destinationsNotCustomizing() = + testScope.runTest { + val destinations by collectLastValue(underTest.destinationScenes) + qsFlexiglassAdapter.setCustomizing(false) + + assertThat(destinations) + .isEqualTo( + mapOf( + UserAction.Back to SceneModel(SceneKey.Shade), + UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), + ) + ) + } + + @Test + fun destinationsCustomizing() = + testScope.runTest { + val destinations by collectLastValue(underTest.destinationScenes) + qsFlexiglassAdapter.setCustomizing(true) + + assertThat(destinations) + .isEqualTo( + mapOf( + UserAction.Back to SceneModel(SceneKey.QuickSettings), + ) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index d1db9c19cd2b..cef888bcc362 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.scene import android.telecom.TelecomManager import android.telephony.TelephonyManager +import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R @@ -39,6 +40,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey @@ -182,6 +184,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private var bouncerSceneJob: Job? = null + private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() }) + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -232,6 +236,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsFlexiglassAdapter, ) utils.deviceEntryRepository.setUnlocked(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index fa849fe806c2..0d3b2b372610 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -76,6 +77,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { scope = testScope.backgroundScope, ) + private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() } + private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel private lateinit var underTest: ShadeSceneViewModel @@ -97,6 +100,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsFlexiglassAdapter, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 86a5c52bd983..027c11c040fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -121,6 +121,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; @@ -329,6 +331,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final DumpManager mDumpManager = new DumpManager(); private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager); + private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags(); + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); @@ -555,7 +559,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mAlternateBouncerInteractor, mUserTracker, () -> mFingerprintManager, - mActivityStarter + mActivityStarter, + mSceneContainerFlags ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt new file mode 100644 index 000000000000..2902c3f65a16 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -0,0 +1,54 @@ +/* + * 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.qs.ui.adapter + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull + +class FakeQSSceneAdapter( + private val inflateDelegate: suspend (Context, ViewGroup?) -> View, +) : QSSceneAdapter { + private val _customizing = MutableStateFlow(false) + override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow() + + private val _view = MutableStateFlow<View?>(null) + override val qsView: Flow<View> = _view.filterNotNull() + + private val _state = MutableStateFlow<QSSceneAdapter.State?>(null) + val state = _state.filterNotNull() + + override suspend fun inflate(context: Context, parent: ViewGroup?) { + _view.value = inflateDelegate(context, parent) + } + + override fun setState(state: QSSceneAdapter.State) { + if (_view.value != null) { + _state.value = state + } + } + + fun setCustomizing(value: Boolean) { + _customizing.value = value + } +} |