Merge "Create a QSSceneInteractor" into main
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 c84a5e9..0000000
--- 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 0000000..28a4801
--- /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 a33eac5..b9451d1 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 @@
) : 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 @@
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 @@
) {
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 @@
)
}
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 7ecfb62..fadbdce 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 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 be85bee..5616175 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 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 13ebdf9..a02f046 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.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.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 @@
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 b0f54ee..0644237 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.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 @@
private boolean mClippingEnabled;
private boolean mIsFullWidth;
+ private boolean mSceneContainerEnabled;
+
public QSContainerImpl(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -72,6 +74,10 @@
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
+ void setSceneContainerEnabled(boolean enabled) {
+ mSceneContainerEnabled = enabled;
+ }
+
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -161,7 +167,7 @@
}
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 73a5faa..7b001c7 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 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 @@
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
}
};
+ private final boolean mSceneContainerEnabled;
private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() {
@Override
@@ -64,18 +66,21 @@
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 fab7e95..7f91fd2 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 @@
private boolean mStackScrollerOverscrolling;
private QSAnimator mQSAnimator;
+ @Nullable
private HeightListener mPanelView;
private QSSquishinessController mQSSquishinessController;
protected QuickStatusBarHeader mHeader;
@@ -340,6 +341,7 @@
}
if (mQSCustomizerController != null) {
mQSCustomizerController.setQs(null);
+ mQSCustomizerController.setContainerController(null);
}
mScrollListener = null;
if (mContainer != null) {
@@ -347,6 +349,10 @@
}
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 @@
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 @@
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 3227f75..ddd7d67 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 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 @@
*/
private boolean mCanCollapse = true;
+ private boolean mSceneContainerEnabled;
+
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -153,6 +155,13 @@
}
}
+ void setSceneContainerEnabled(boolean enabled) {
+ mSceneContainerEnabled = enabled;
+ if (mSceneContainerEnabled) {
+ updatePadding();
+ }
+ }
+
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -375,7 +384,7 @@
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 6bbdc54..5eb9620 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.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 @@
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 @@
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 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mSceneContainerEnabled = sceneContainerFlags.isEnabled();
}
@Override
@@ -116,6 +121,7 @@
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 67c4208..c657b55 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 @@
protected QuickQSPanel mHeaderQsPanel;
+ private boolean mSceneContainerEnabled;
+
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -52,6 +54,13 @@
updateResources();
}
+ void setSceneContainerEnabled(boolean enabled) {
+ mSceneContainerEnabled = enabled;
+ if (mSceneContainerEnabled) {
+ updateResources();
+ }
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -82,7 +91,9 @@
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 64960e6..1d92d78 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 @@
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 024e760..c28371c 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 @@
/** 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 bd4c6e1..1aef920 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.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.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 @@
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
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
rename to packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
index ba1aa62..b942368 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 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
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
rename to packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
index 36fac44..446cb62 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 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 0000000..b4340f5
--- /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 5993cf1..3941fd7 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 @@
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 @@
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 5a4d876..4ca763f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -106,12 +106,12 @@
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
*/
@@ -720,7 +720,9 @@
/** 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 20b9ede..af88081 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 @@
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 @@
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 4586f58..cd7a9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -160,6 +160,7 @@
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;
@@ -584,6 +585,8 @@
private final InteractionJankMonitor mJankMonitor;
+ private final SceneContainerFlags mSceneContainerFlags;
+
/**
* Public constructor for CentralSurfaces.
*
@@ -697,7 +700,8 @@
AlternateBouncerInteractor alternateBouncerInteractor,
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
- ActivityStarter activityStarter
+ ActivityStarter activityStarter,
+ SceneContainerFlags sceneContainerFlags
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -794,6 +798,7 @@
mUserTracker = userTracker;
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
+ mSceneContainerFlags = sceneContainerFlags;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1248,7 +1253,7 @@
// 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 5e4c954..1f7a029 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.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 @@
@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 @@
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 555484c..8acde36 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.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 @@
@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 @@
`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 0000000..c1c0126
--- /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 d582b9e..ef129c8 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.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 @@
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 @@
sceneInteractor = sceneInteractor,
),
shadeHeaderViewModel = shadeHeaderViewModel,
+ qsSceneAdapter = qsFlexiglassAdapter,
)
}
@@ -124,4 +129,33 @@
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 d1db9c1..cef888b 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 @@
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.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 @@
private var bouncerSceneJob: Job? = null
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() })
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -232,6 +236,7 @@
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 fa849fe..0d3b2b3 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.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 @@
scope = testScope.backgroundScope,
)
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() }
+
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var underTest: ShadeSceneViewModel
@@ -97,6 +100,7 @@
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 86a5c52..027c11c 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.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 @@
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 @@
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 0000000..2902c3f
--- /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
+ }
+}