1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 package com.android.systemui.shade.ui.composable
19 
20 import android.view.ContextThemeWrapper
21 import android.view.ViewGroup
22 import androidx.compose.foundation.background
23 import androidx.compose.foundation.clickable
24 import androidx.compose.foundation.interaction.MutableInteractionSource
25 import androidx.compose.foundation.interaction.collectIsHoveredAsState
26 import androidx.compose.foundation.layout.Arrangement
27 import androidx.compose.foundation.layout.Box
28 import androidx.compose.foundation.layout.Column
29 import androidx.compose.foundation.layout.Row
30 import androidx.compose.foundation.layout.RowScope
31 import androidx.compose.foundation.layout.Spacer
32 import androidx.compose.foundation.layout.defaultMinSize
33 import androidx.compose.foundation.layout.fillMaxWidth
34 import androidx.compose.foundation.layout.height
35 import androidx.compose.foundation.layout.padding
36 import androidx.compose.foundation.layout.width
37 import androidx.compose.foundation.layout.widthIn
38 import androidx.compose.foundation.shape.RoundedCornerShape
39 import androidx.compose.material3.ColorScheme
40 import androidx.compose.material3.MaterialTheme
41 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
42 import androidx.compose.runtime.Composable
43 import androidx.compose.runtime.derivedStateOf
44 import androidx.compose.runtime.getValue
45 import androidx.compose.runtime.remember
46 import androidx.compose.ui.Alignment
47 import androidx.compose.ui.Modifier
48 import androidx.compose.ui.draw.clip
49 import androidx.compose.ui.graphics.Color
50 import androidx.compose.ui.graphics.TransformOrigin
51 import androidx.compose.ui.graphics.graphicsLayer
52 import androidx.compose.ui.layout.Layout
53 import androidx.compose.ui.platform.LocalLayoutDirection
54 import androidx.compose.ui.res.stringResource
55 import androidx.compose.ui.unit.Constraints
56 import androidx.compose.ui.unit.LayoutDirection
57 import androidx.compose.ui.unit.dp
58 import androidx.compose.ui.unit.max
59 import androidx.compose.ui.viewinterop.AndroidView
60 import androidx.lifecycle.compose.collectAsStateWithLifecycle
61 import com.android.compose.animation.scene.ElementKey
62 import com.android.compose.animation.scene.LowestZIndexScenePicker
63 import com.android.compose.animation.scene.SceneScope
64 import com.android.compose.animation.scene.TransitionState
65 import com.android.compose.animation.scene.ValueKey
66 import com.android.compose.animation.scene.animateElementFloatAsState
67 import com.android.compose.modifiers.thenIf
68 import com.android.compose.windowsizeclass.LocalWindowSizeClass
69 import com.android.settingslib.Utils
70 import com.android.systemui.battery.BatteryMeterView
71 import com.android.systemui.battery.BatteryMeterViewController
72 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
73 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
74 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
75 import com.android.systemui.compose.modifiers.sysuiResTag
76 import com.android.systemui.privacy.OngoingPrivacyChip
77 import com.android.systemui.res.R
78 import com.android.systemui.scene.shared.model.Scenes
79 import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim
80 import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
81 import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale
82 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
83 import com.android.systemui.statusbar.phone.StatusBarLocation
84 import com.android.systemui.statusbar.phone.StatusIconContainer
85 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
86 import com.android.systemui.statusbar.phone.ui.TintedIconManager
87 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView
88 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel
89 import com.android.systemui.statusbar.policy.Clock
90 
91 object ShadeHeader {
92     object Elements {
93         val ExpandedContent = ElementKey("ShadeHeaderExpandedContent")
94         val CollapsedContentStart = ElementKey("ShadeHeaderCollapsedContentStart")
95         val CollapsedContentEnd = ElementKey("ShadeHeaderCollapsedContentEnd")
96         val PrivacyChip = ElementKey("PrivacyChip", scenePicker = LowestZIndexScenePicker)
97         val Clock = ElementKey("ShadeHeaderClock", scenePicker = LowestZIndexScenePicker)
98         val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup")
99     }
100 
101     object Values {
102         val ClockScale = ValueKey("ShadeHeaderClockScale")
103     }
104 
105     object Dimensions {
106         val CollapsedHeight = 48.dp
107         val ExpandedHeight = 120.dp
108     }
109 
110     object Colors {
111         val ColorScheme.shadeHeaderText: Color
112             get() = Color.White
113         val ColorScheme.onScrimDim: Color
114             get() = Color.DarkGray
115     }
116 
117     object TestTags {
118         const val Root = "shade_header_root"
119     }
120 }
121 
122 @Composable
CollapsedShadeHeadernull123 fun SceneScope.CollapsedShadeHeader(
124     viewModel: ShadeHeaderViewModel,
125     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
126     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
127     statusBarIconController: StatusBarIconController,
128     modifier: Modifier = Modifier,
129 ) {
130     val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
131     if (isDisabled) {
132         return
133     }
134 
135     val cutoutWidth = LocalDisplayCutout.current.width()
136     val cutoutHeight = LocalDisplayCutout.current.height()
137     val cutoutTop = LocalDisplayCutout.current.top
138     val cutoutLocation = LocalDisplayCutout.current.location
139     val horizontalPadding =
140         max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
141 
142     val useExpandedTextFormat by
143         remember(cutoutLocation) {
144             derivedStateOf {
145                 cutoutLocation != CutoutLocation.CENTER ||
146                     shouldUseExpandedFormat(layoutState.transitionState)
147             }
148         }
149 
150     val isLargeScreenLayout =
151             LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Medium ||
152                     LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded
153 
154     val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
155 
156     // This layout assumes it is globally positioned at (0, 0) and is the
157     // same size as the screen.
158     Layout(
159         modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root),
160         contents =
161             listOf(
162                 {
163                     Row(modifier = Modifier.padding(horizontal = horizontalPadding)) {
164                         Clock(
165                             scale = 1f,
166                             viewModel = viewModel,
167                             modifier = Modifier.align(Alignment.CenterVertically),
168                         )
169                         Spacer(modifier = Modifier.width(5.dp))
170                         VariableDayDate(
171                             viewModel = viewModel,
172                             modifier =
173                                 Modifier.element(ShadeHeader.Elements.CollapsedContentStart)
174                                     .align(Alignment.CenterVertically),
175                         )
176                     }
177                 },
178                 {
179                     if (isPrivacyChipVisible) {
180                         Box(
181                             modifier =
182                                 Modifier.height(CollapsedHeight)
183                                     .fillMaxWidth()
184                                     .padding(horizontal = horizontalPadding)
185                         ) {
186                             PrivacyChip(
187                                 viewModel = viewModel,
188                                 modifier = Modifier.align(Alignment.CenterEnd),
189                             )
190                         }
191                     } else {
192                         Row(
193                             horizontalArrangement = Arrangement.End,
194                             modifier =
195                                 Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
196                                     .padding(horizontal = horizontalPadding)
197                         ) {
198                             if (isLargeScreenLayout) {
199                                 ShadeCarrierGroup(
200                                         viewModel = viewModel,
201                                         modifier = Modifier.align(Alignment.CenterVertically),
202                                 )
203                             }
204                             SystemIconContainer(
205                                 viewModel = viewModel,
206                                 isClickable = isLargeScreenLayout,
207                                 modifier = Modifier.align(Alignment.CenterVertically)
208                             ) {
209                                 StatusIcons(
210                                     viewModel = viewModel,
211                                     createTintedIconManager = createTintedIconManager,
212                                     statusBarIconController = statusBarIconController,
213                                     useExpandedFormat = useExpandedTextFormat,
214                                     modifier =
215                                         Modifier.align(Alignment.CenterVertically)
216                                             .padding(end = 6.dp)
217                                             .weight(1f, fill = false)
218                                 )
219                                 BatteryIcon(
220                                     createBatteryMeterViewController =
221                                         createBatteryMeterViewController,
222                                     useExpandedFormat = useExpandedTextFormat,
223                                     modifier = Modifier.align(Alignment.CenterVertically),
224                                 )
225                             }
226                         }
227                     }
228                 },
229             ),
230     ) { measurables, constraints ->
231         check(constraints.hasBoundedWidth)
232         check(measurables.size == 2)
233         check(measurables[0].size == 1)
234         check(measurables[1].size == 1)
235 
236         val screenWidth = constraints.maxWidth
237         val cutoutWidthPx = cutoutWidth.roundToPx()
238         val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx()
239         val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
240 
241         val startMeasurable = measurables[0][0]
242         val endMeasurable = measurables[1][0]
243 
244         val startPlaceable = startMeasurable.measure(childConstraints)
245         val endPlaceable = endMeasurable.measure(childConstraints)
246 
247         layout(screenWidth, height) {
248             when (cutoutLocation) {
249                 CutoutLocation.NONE,
250                 CutoutLocation.RIGHT -> {
251                     startPlaceable.placeRelative(x = 0, y = 0)
252                     endPlaceable.placeRelative(
253                         x = startPlaceable.width,
254                         y = 0,
255                     )
256                 }
257                 CutoutLocation.CENTER -> {
258                     startPlaceable.placeRelative(x = 0, y = 0)
259                     endPlaceable.placeRelative(
260                         x = startPlaceable.width + cutoutWidthPx,
261                         y = 0,
262                     )
263                 }
264                 CutoutLocation.LEFT -> {
265                     startPlaceable.placeRelative(
266                         x = cutoutWidthPx,
267                         y = 0,
268                     )
269                     endPlaceable.placeRelative(
270                         x = startPlaceable.width + cutoutWidthPx,
271                         y = 0,
272                     )
273                 }
274             }
275         }
276     }
277 }
278 
279 @Composable
ExpandedShadeHeadernull280 fun SceneScope.ExpandedShadeHeader(
281     viewModel: ShadeHeaderViewModel,
282     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
283     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
284     statusBarIconController: StatusBarIconController,
285     modifier: Modifier = Modifier,
286 ) {
287     val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
288     if (isDisabled) {
289         return
290     }
291 
292     val useExpandedFormat by remember {
293         derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
294     }
295 
296     val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
297 
298     Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
299         if (isPrivacyChipVisible) {
300             Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
301                 PrivacyChip(
302                     viewModel = viewModel,
303                     modifier = Modifier.align(Alignment.CenterEnd),
304                 )
305             }
306         }
307         Column(
308             verticalArrangement = Arrangement.Bottom,
309             modifier =
310                 Modifier.fillMaxWidth()
311                     .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
312         ) {
313             Box(modifier = Modifier.fillMaxWidth()) {
314                 Box {
315                     Clock(
316                         scale = 2.57f,
317                         viewModel = viewModel,
318                         modifier = Modifier.align(Alignment.CenterStart),
319                     )
320                 }
321                 Box(
322                     modifier =
323                         Modifier.element(ShadeHeader.Elements.ShadeCarrierGroup).fillMaxWidth()
324                 ) {
325                     ShadeCarrierGroup(
326                         viewModel = viewModel,
327                         modifier = Modifier.align(Alignment.CenterEnd),
328                     )
329                 }
330             }
331             Spacer(modifier = Modifier.width(5.dp))
332             Row(modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent)) {
333                 VariableDayDate(
334                     viewModel = viewModel,
335                     modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically),
336                 )
337                 Spacer(modifier = Modifier.weight(1f))
338                 SystemIconContainer(viewModel = viewModel, isClickable = false) {
339                     StatusIcons(
340                         viewModel = viewModel,
341                         createTintedIconManager = createTintedIconManager,
342                         statusBarIconController = statusBarIconController,
343                         useExpandedFormat = useExpandedFormat,
344                         modifier =
345                             Modifier.align(Alignment.CenterVertically)
346                                 .padding(end = 6.dp)
347                                 .weight(1f, fill = false),
348                     )
349                     BatteryIcon(
350                         useExpandedFormat = useExpandedFormat,
351                         createBatteryMeterViewController = createBatteryMeterViewController,
352                         modifier = Modifier.align(Alignment.CenterVertically),
353                     )
354                 }
355             }
356         }
357     }
358 }
359 
360 @Composable
SceneScopenull361 private fun SceneScope.Clock(
362     scale: Float,
363     viewModel: ShadeHeaderViewModel,
364     modifier: Modifier,
365 ) {
366     val layoutDirection = LocalLayoutDirection.current
367 
368     Element(key = ShadeHeader.Elements.Clock, modifier = modifier) {
369         val animatedScale by animateElementFloatAsState(scale, ClockScale, canOverflow = false)
370         AndroidView(
371             factory = { context ->
372                 Clock(
373                     ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings_Header),
374                     null,
375                 )
376             },
377             modifier =
378                 modifier
379                     // use graphicsLayer instead of Modifier.scale to anchor transform
380                     // to the (start, top) corner
381                     .graphicsLayer {
382                         scaleX = animatedScale
383                         scaleY = animatedScale
384                         transformOrigin =
385                             TransformOrigin(
386                                 when (layoutDirection) {
387                                     LayoutDirection.Ltr -> 0f
388                                     LayoutDirection.Rtl -> 1f
389                                 },
390                                 0.5f
391                             )
392                     }
393                     .clickable { viewModel.onClockClicked() }
394         )
395     }
396 }
397 
398 @Composable
BatteryIconnull399 private fun BatteryIcon(
400     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
401     useExpandedFormat: Boolean,
402     modifier: Modifier = Modifier,
403 ) {
404     AndroidView(
405         factory = { context ->
406             val batteryIcon = BatteryMeterView(context, null)
407             batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ON)
408 
409             val themedContext =
410                 ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings_Header)
411             val fg = Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary)
412             val bg =
413                 Utils.getColorAttrDefaultColor(
414                     themedContext,
415                     android.R.attr.textColorPrimaryInverse,
416                 )
417 
418             // [BatteryMeterView.updateColors] is an old method that was built to distinguish
419             // between dual-tone colors and single-tone. The current icon is only single-tone, so
420             // the final [fg] is the only one we actually need
421             batteryIcon.updateColors(fg, bg, fg)
422 
423             val batteryMaterViewController =
424                 createBatteryMeterViewController(batteryIcon, StatusBarLocation.QS)
425             batteryMaterViewController.init()
426             batteryMaterViewController.ignoreTunerUpdates()
427 
428             batteryIcon
429         },
430         update = { batteryIcon ->
431             // TODO(b/298525212): use MODE_ESTIMATE in collapsed view when the screen
432             //  has no center cutout. See [QsBatteryModeController.getBatteryMode]
433             batteryIcon.setPercentShowMode(
434                 if (useExpandedFormat) {
435                     BatteryMeterView.MODE_ESTIMATE
436                 } else {
437                     BatteryMeterView.MODE_ON
438                 }
439             )
440         },
441         modifier = modifier,
442     )
443 }
444 
445 @Composable
ShadeCarrierGroupnull446 private fun ShadeCarrierGroup(
447     viewModel: ShadeHeaderViewModel,
448     modifier: Modifier = Modifier,
449 ) {
450     Row(modifier = modifier) {
451         val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle()
452 
453         for (subId in subIds) {
454             Spacer(modifier = Modifier.width(5.dp))
455             AndroidView(
456                 factory = { context ->
457                     ModernShadeCarrierGroupMobileView.constructAndBind(
458                             context = context,
459                             logger = viewModel.mobileIconsViewModel.logger,
460                             slot = "mobile_carrier_shade_group",
461                             viewModel =
462                                 (viewModel.mobileIconsViewModel.viewModelForSub(
463                                     subId,
464                                     StatusBarLocation.SHADE_CARRIER_GROUP
465                                 ) as ShadeCarrierGroupMobileIconViewModel),
466                         )
467                         .also { it.setOnClickListener { viewModel.onShadeCarrierGroupClicked() } }
468                 },
469             )
470         }
471     }
472 }
473 
474 @Composable
StatusIconsnull475 private fun SceneScope.StatusIcons(
476     viewModel: ShadeHeaderViewModel,
477     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
478     statusBarIconController: StatusBarIconController,
479     useExpandedFormat: Boolean,
480     modifier: Modifier = Modifier,
481 ) {
482     val carrierIconSlots =
483         listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile))
484     val cameraSlot = stringResource(id = com.android.internal.R.string.status_bar_camera)
485     val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone)
486     val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location)
487 
488     val isSingleCarrier by viewModel.isSingleCarrier.collectAsStateWithLifecycle()
489     val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsStateWithLifecycle()
490     val isMicCameraIndicationEnabled by
491         viewModel.isMicCameraIndicationEnabled.collectAsStateWithLifecycle()
492     val isLocationIndicationEnabled by
493         viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle()
494 
495     AndroidView(
496         factory = { context ->
497             val themedContext =
498                 ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings_Header)
499             val iconContainer = StatusIconContainer(themedContext, null)
500             val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS)
501             iconManager.setTint(
502                 Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary),
503                 Utils.getColorAttrDefaultColor(
504                     themedContext,
505                     android.R.attr.textColorPrimaryInverse
506                 ),
507             )
508             statusBarIconController.addIconGroup(iconManager)
509 
510             iconContainer
511         },
512         update = { iconContainer ->
513             iconContainer.setQsExpansionTransitioning(
514                 layoutState.isTransitioningBetween(Scenes.Shade, Scenes.QuickSettings)
515             )
516             if (isSingleCarrier || !useExpandedFormat) {
517                 iconContainer.removeIgnoredSlots(carrierIconSlots)
518             } else {
519                 iconContainer.addIgnoredSlots(carrierIconSlots)
520             }
521 
522             if (isPrivacyChipEnabled) {
523                 if (isMicCameraIndicationEnabled) {
524                     iconContainer.addIgnoredSlot(cameraSlot)
525                     iconContainer.addIgnoredSlot(micSlot)
526                 } else {
527                     iconContainer.removeIgnoredSlot(cameraSlot)
528                     iconContainer.removeIgnoredSlot(micSlot)
529                 }
530                 if (isLocationIndicationEnabled) {
531                     iconContainer.addIgnoredSlot(locationSlot)
532                 } else {
533                     iconContainer.removeIgnoredSlot(locationSlot)
534                 }
535             } else {
536                 iconContainer.removeIgnoredSlot(cameraSlot)
537                 iconContainer.removeIgnoredSlot(micSlot)
538                 iconContainer.removeIgnoredSlot(locationSlot)
539             }
540         },
541         modifier = modifier,
542     )
543 }
544 
545 @Composable
SystemIconContainernull546 private fun SystemIconContainer(
547     viewModel: ShadeHeaderViewModel,
548     isClickable: Boolean,
549     modifier: Modifier = Modifier,
550     content: @Composable RowScope.() -> Unit
551 ) {
552     val interactionSource = remember { MutableInteractionSource() }
553     val isHovered by interactionSource.collectIsHoveredAsState()
554 
555     val hoverModifier = Modifier
556             .clip(RoundedCornerShape(CollapsedHeight / 4))
557             .background(MaterialTheme.colorScheme.onScrimDim)
558 
559     Row(
560         modifier = modifier
561                 .height(CollapsedHeight)
562                 .padding(vertical = CollapsedHeight / 4)
563                 .thenIf(isClickable) {
564                     Modifier.clickable(
565                             interactionSource = interactionSource,
566                             indication = null,
567                             onClick = { viewModel.onSystemIconContainerClicked() },
568                     )
569                 }
570                 .thenIf(isHovered) { hoverModifier },
571         content = content,
572     )
573 }
574 
575 @Composable
PrivacyChipnull576 private fun SceneScope.PrivacyChip(
577     viewModel: ShadeHeaderViewModel,
578     modifier: Modifier = Modifier,
579 ) {
580     val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle()
581 
582     AndroidView(
583         factory = { context ->
584             val view =
585                 OngoingPrivacyChip(context, null).also { privacyChip ->
586                     privacyChip.privacyList = privacyList
587                     privacyChip.setOnClickListener { viewModel.onPrivacyChipClicked(privacyChip) }
588                 }
589             view
590         },
591         update = { it.privacyList = privacyList },
592         modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
593     )
594 }
595 
shouldUseExpandedFormatnull596 private fun shouldUseExpandedFormat(state: TransitionState): Boolean {
597     return when (state) {
598         is TransitionState.Idle -> {
599             state.currentScene == Scenes.QuickSettings
600         }
601         is TransitionState.Transition -> {
602             ((state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) ||
603                 state.isTransitioning(Scenes.Gone, Scenes.QuickSettings) ||
604                 state.isTransitioning(Scenes.Lockscreen, Scenes.QuickSettings)) &&
605                 state.progress >= 0.5) ||
606                 ((state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) ||
607                     state.isTransitioning(Scenes.QuickSettings, Scenes.Gone) ||
608                     state.isTransitioning(Scenes.QuickSettings, Scenes.Lockscreen)) &&
609                     state.progress <= 0.5)
610         }
611     }
612 }
613