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 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
18
19 import android.annotation.ColorInt
20 import android.graphics.Rect
21 import android.view.Display
22 import android.view.View
23 import androidx.compose.runtime.getValue
24 import com.android.app.tracing.FlowTracing.traceEach
25 import com.android.app.tracing.TrackGroupUtils.trackGroup
26 import com.android.app.tracing.coroutines.launchTraced as launch
27 import com.android.systemui.dagger.qualifiers.Background
28 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
29 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
30 import com.android.systemui.keyguard.shared.model.Edge
31 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
32 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
33 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
34 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
35 import com.android.systemui.keyguard.shared.model.TransitionState
36 import com.android.systemui.lifecycle.Activatable
37 import com.android.systemui.lifecycle.ExclusiveActivatable
38 import com.android.systemui.lifecycle.Hydrator
39 import com.android.systemui.log.table.TableLogBufferFactory
40 import com.android.systemui.log.table.logDiffsForTable
41 import com.android.systemui.plugins.DarkIconDispatcher
42 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
43 import com.android.systemui.scene.domain.interactor.SceneInteractor
44 import com.android.systemui.scene.shared.flag.SceneContainerFlag
45 import com.android.systemui.scene.shared.model.Overlays
46 import com.android.systemui.scene.shared.model.Scenes
47 import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
48 import com.android.systemui.shade.domain.interactor.ShadeInteractor
49 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
50 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
51 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
52 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
53 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
54 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
55 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
56 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
57 import com.android.systemui.statusbar.chips.uievents.StatusBarChipsUiEventLogger
58 import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
59 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
60 import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
61 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
62 import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
63 import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
64 import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModelStore
65 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
66 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
67 import com.android.systemui.statusbar.notification.headsup.PinnedStatus
68 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
69 import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
70 import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
71 import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
72 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
73 import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
74 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
75 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
76 import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
77 import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
78 import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
79 import dagger.assisted.Assisted
80 import dagger.assisted.AssistedFactory
81 import dagger.assisted.AssistedInject
82 import javax.inject.Provider
83 import kotlinx.coroutines.CoroutineDispatcher
84 import kotlinx.coroutines.CoroutineScope
85 import kotlinx.coroutines.awaitCancellation
86 import kotlinx.coroutines.coroutineScope
87 import kotlinx.coroutines.flow.Flow
88 import kotlinx.coroutines.flow.SharingStarted
89 import kotlinx.coroutines.flow.StateFlow
90 import kotlinx.coroutines.flow.combine
91 import kotlinx.coroutines.flow.conflate
92 import kotlinx.coroutines.flow.distinctUntilChanged
93 import kotlinx.coroutines.flow.emptyFlow
94 import kotlinx.coroutines.flow.filter
95 import kotlinx.coroutines.flow.flowOf
96 import kotlinx.coroutines.flow.flowOn
97 import kotlinx.coroutines.flow.map
98 import kotlinx.coroutines.flow.stateIn
99
100 /**
101 * A view model that manages the visibility of the [CollapsedStatusBarFragment] based on the device
102 * state.
103 *
104 * Right now, most of the status bar visibility management is actually in
105 * [CollapsedStatusBarFragment.calculateInternalModel], which uses
106 * [CollapsedStatusBarFragment.shouldHideNotificationIcons] and
107 * [StatusBarHideIconsForBouncerManager]. We should move those pieces of logic to this class instead
108 * so that it's all in one place and easily testable outside of the fragment.
109 */
110 interface HomeStatusBarViewModel : Activatable {
111 /** Factory to create the view model for the battery icon */
112 val batteryViewModelFactory: BatteryViewModel.Factory
113
114 /**
115 * True if the device is currently transitioning from lockscreen to occluded and false
116 * otherwise.
117 */
118 val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean>
119
120 /** Emits whenever a transition from lockscreen to dream has started. */
121 val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
122
123 /**
124 * The current media projection stop dialog to be shown, or
125 * `MediaProjectionStopDialogModel.Hidden` if no dialog is visible.
126 */
127 val mediaProjectionStopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel>
128
129 /**
130 * The ongoing activity chip that should be primarily shown on the left-hand side of the status
131 * bar. If there are multiple ongoing activity chips, this one should take priority.
132 */
133 val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel>
134
135 /** All supported activity chips, whether they are currently active or not. */
136 val ongoingActivityChips: ChipsVisibilityModel
137
138 /**
139 * The multiple ongoing activity chips that should be shown on the left-hand side of the status
140 * bar.
141 */
142 @Deprecated("Since StatusBarChipsModernization, use the new ongoingActivityChips")
143 val ongoingActivityChipsLegacy: StateFlow<MultipleOngoingActivityChipsModelLegacy>
144
145 /** View model for the carrier name that may show in the status bar based on carrier config */
146 val operatorNameViewModel: StatusBarOperatorNameViewModel
147
148 /** The popup chips that should be shown on the right-hand side of the status bar. */
149 val popupChips: List<PopupChipModel.Shown>
150
151 /**
152 * True if the status bar should be visible.
153 *
154 * TODO(b/364360986): Once the is<SomeChildView>Visible flows are fully enabled, we shouldn't
155 * need this flow anymore.
156 */
157 val isHomeStatusBarAllowed: StateFlow<Boolean>
158
159 /** True if the home status bar is showing, and there is no HUN happening */
160 val canShowOngoingActivityChips: Flow<Boolean>
161
162 /** True if the operator name view is not hidden due to HUN or other visibility state */
163 val shouldShowOperatorNameView: Flow<Boolean>
164 val isClockVisible: Flow<VisibilityModel>
165 val isNotificationIconContainerVisible: Flow<VisibilityModel>
166
167 /**
168 * Pair of (system info visibility, event animation state). The animation state can be used to
169 * respond to the system event chip animations. In all cases, system info visibility correctly
170 * models the View.visibility for the system info area
171 */
172 val systemInfoCombinedVis: StateFlow<SystemInfoCombinedVisibilityModel>
173
174 /** Which icons to block from the home status bar */
175 val iconBlockList: Flow<List<String>>
176
177 /** This status bar's current content area for the given rotation in absolute bounds. */
178 val contentArea: Flow<Rect>
179
180 /**
181 * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
182 * status bar and navigation icons dim. In this mode, a notification dot appears where the
183 * notification icons would appear if they would be shown outside of this mode.
184 *
185 * This flow tells when to show or hide the notification dot in the status bar to indicate
186 * whether there are notifications when the device is in
187 * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
188 */
189 val areNotificationsLightsOut: Flow<Boolean>
190
191 /**
192 * A flow of [StatusBarTintColor], a functional interface that will allow a view to calculate
193 * its correct tint depending on location
194 */
195 val areaTint: Flow<StatusBarTintColor>
196
197 /** [IsAreaDark] applicable for this status bar's display and content area */
198 val areaDark: IsAreaDark
199
200 /** Interface for the assisted factory, to allow for providing a fake in tests */
201 interface HomeStatusBarViewModelFactory {
202 fun create(displayId: Int): HomeStatusBarViewModel
203 }
204 }
205
206 class HomeStatusBarViewModelImpl
207 @AssistedInject
208 constructor(
209 @Assisted thisDisplayId: Int,
210 override val batteryViewModelFactory: BatteryViewModel.Factory,
211 tableLoggerFactory: TableLogBufferFactory,
212 homeStatusBarInteractor: HomeStatusBarInteractor,
213 homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
214 lightsOutInteractor: LightsOutInteractor,
215 notificationsInteractor: ActiveNotificationsInteractor,
216 darkIconInteractor: DarkIconInteractor,
217 headsUpNotificationInteractor: HeadsUpNotificationInteractor,
218 keyguardTransitionInteractor: KeyguardTransitionInteractor,
219 keyguardInteractor: KeyguardInteractor,
220 override val operatorNameViewModel: StatusBarOperatorNameViewModel,
221 sceneInteractor: SceneInteractor,
222 sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
223 shadeInteractor: ShadeInteractor,
224 shareToAppChipViewModel: ShareToAppChipViewModel,
225 ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
226 statusBarPopupChipsViewModelFactory: StatusBarPopupChipsViewModel.Factory,
227 animations: SystemStatusEventAnimationInteractor,
228 statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
229 @Background bgScope: CoroutineScope,
230 @Background bgDispatcher: CoroutineDispatcher,
231 shadeDisplaysInteractor: Provider<ShadeDisplaysInteractor>,
232 private val uiEventLogger: StatusBarChipsUiEventLogger,
233 ) : HomeStatusBarViewModel, ExclusiveActivatable() {
234
235 private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
236
237 val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
238
<lambda>null239 private val statusBarPopupChips by lazy { statusBarPopupChipsViewModelFactory.create() }
240
241 override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
242 keyguardTransitionInteractor
243 .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
244 .distinctUntilChanged()
245 .logDiffsForTable(
246 tableLogBuffer = tableLogger,
247 columnName = COL_LOCK_TO_OCCLUDED,
248 initialValue = false,
249 )
250 .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false)
251
252 override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
253 keyguardTransitionInteractor
254 .transition(Edge.create(from = LOCKSCREEN, to = DREAMING))
<lambda>null255 .filter { it.transitionState == TransitionState.STARTED }
<lambda>null256 .map {}
257 .flowOn(bgDispatcher)
258
259 override val mediaProjectionStopDialogDueToCallEndedState =
260 shareToAppChipViewModel.stopDialogToShow
261
262 override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
263
264 override val ongoingActivityChipsLegacy = ongoingActivityChipsViewModel.chipsLegacy
265
266 override val popupChips
267 get() = statusBarPopupChips.shownPopupChips
268
269 private val isShadeExpandedEnough =
270 // Keep the status bar visible while the shade is just starting to open, but otherwise
271 // hide it so that the status bar doesn't draw while it can't be seen.
272 // See b/394257529#comment24.
<lambda>null273 shadeInteractor.anyExpansion.map { it >= 0.2 }.distinctUntilChanged()
274
275 /**
276 * Whether the display of this statusbar has the shade window (that is hosting shade container
277 * and lockscreen, among other things).
278 */
279 private val isShadeWindowOnThisDisplay =
280 if (ShadeWindowGoesAround.isEnabled) {
shadeDisplayIdnull281 shadeDisplaysInteractor.get().displayId.map { shadeDisplayId ->
282 thisDisplayId == shadeDisplayId
283 }
284 } else {
285 // Shade doesn't move anywhere, it is always on the default display.
286 flowOf(thisDisplayId == Display.DEFAULT_DISPLAY)
287 }
288
289 private val isShadeVisibleOnAnyDisplay =
290 if (SceneContainerFlag.isEnabled) {
currentOverlaysnull291 sceneInteractor.currentOverlays.map { currentOverlays ->
292 (Overlays.NotificationsShade in currentOverlays ||
293 Overlays.QuickSettingsShade in currentOverlays)
294 }
295 } else {
296 isShadeExpandedEnough
297 }
298
299 private val isShadeVisibleOnThisDisplay: Flow<Boolean> =
300 combine(isShadeWindowOnThisDisplay, isShadeVisibleOnAnyDisplay) {
hasShadenull301 hasShade,
302 isShadeVisibleOnAnyDisplay ->
303 hasShade && isShadeVisibleOnAnyDisplay
304 }
305
306 private val isHomeStatusBarAllowedByScene: Flow<Boolean> =
307 combine(
308 sceneInteractor.currentScene,
309 isShadeVisibleOnThisDisplay,
310 sceneContainerOcclusionInteractor.invisibleDueToOcclusion,
311 ) { currentScene, isShadeVisible, isOccluded ->
312
313 // All scenes have their own status bars, so we should only show the home status bar
314 // if we're not in a scene. There are two exceptions:
315 // 1) The shade (notifications or quick settings) is shown, because it has its own
316 // status-bar-like header.
317 // 2) If the scene is occluded, then the occluding app needs to show the status bar.
318 // (Fullscreen apps actually won't show the status bar but that's handled with the
319 // rest of our fullscreen app logic, which lives elsewhere.)
320 (currentScene == Scenes.Gone && !isShadeVisible) || isOccluded
321 }
322 .distinctUntilChanged()
323 .logDiffsForTable(
324 tableLogBuffer = tableLogger,
325 columnName = COL_ALLOWED_BY_SCENE,
326 initialValue = false,
327 )
328
329 override val areNotificationsLightsOut: Flow<Boolean> =
330 if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
331 emptyFlow()
332 } else {
333 combine(
334 notificationsInteractor.areAnyNotificationsPresent,
335 lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
hasNotificationsnull336 ) { hasNotifications, isLowProfile ->
337 hasNotifications && isLowProfile
338 }
339 .distinctUntilChanged()
340 }
341 .logDiffsForTable(
342 tableLogBuffer = tableLogger,
343 columnName = COL_NOTIF_LIGHTS_OUT,
344 initialValue = false,
345 )
346 .flowOn(bgDispatcher)
347
348 override val areaTint: Flow<StatusBarTintColor> =
349 darkIconInteractor
350 .darkState(thisDisplayId)
areasnull351 .map { (areas: Collection<Rect>, tint: Int) ->
352 StatusBarTintColor { viewBounds: Rect ->
353 if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
354 tint
355 } else {
356 DarkIconDispatcher.DEFAULT_ICON_TINT
357 }
358 }
359 }
360 .conflate()
361 .distinctUntilChanged()
362 .flowOn(bgDispatcher)
363
364 override val areaDark: IsAreaDark by
365 hydrator.hydratedStateOf(
366 traceName = "areaDark",
<lambda>null367 initialValue = IsAreaDark { true },
368 source = darkIconInteractor.isAreaDark(thisDisplayId),
369 )
370
371 /**
372 * True if the current SysUI state can show the home status bar (aka this status bar), and false
373 * if we shouldn't be showing any part of the home status bar.
374 */
375 private val isHomeScreenStatusBarAllowedLegacy: Flow<Boolean> =
376 combine(keyguardTransitionInteractor.currentKeyguardState, isShadeVisibleOnThisDisplay) {
currentKeyguardStatenull377 currentKeyguardState,
378 isShadeVisibleOnThisDisplay ->
379 (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) &&
380 !isShadeVisibleOnThisDisplay
381 // TODO(b/364360986): Add edge cases, like secure camera launch.
382 }
383
384 // "Compat" to cover both legacy and Scene container case in one flow.
385 private val isHomeStatusBarAllowedCompat =
386 if (SceneContainerFlag.isEnabled) {
387 isHomeStatusBarAllowedByScene
388 } else {
389 isHomeScreenStatusBarAllowedLegacy
390 }
391
392 override val isHomeStatusBarAllowed =
393 isHomeStatusBarAllowedCompat
394 .traceEach(trackGroup(TRACK_GROUP, "isHomeStatusBarAllowed"), logcat = true)
395 .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false)
396
397 private val shouldHomeStatusBarBeVisible =
398 combine(
399 isHomeStatusBarAllowed,
400 keyguardInteractor.isSecureCameraActive,
401 headsUpNotificationInteractor.statusBarHeadsUpStatus,
headsUpStatenull402 ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
403 // When launching the camera over the lockscreen, the status icons would typically
404 // become visible momentarily before animating out, since we're not yet aware that
405 // the launching camera activity is fullscreen. Even once the activity finishes
406 // launching, it takes a short time before WM decides that the top app wants to hide
407 // the icons and tells us to hide them. To ensure that this high-visibility
408 // animation is smooth, keep the icons hidden during a camera launch. See
409 // b/257292822.
410 headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
411 }
412 .distinctUntilChanged()
413 .logDiffsForTable(
414 tableLogBuffer = tableLogger,
415 columnName = COL_VISIBLE,
416 initialValue = false,
417 )
418 .flowOn(bgDispatcher)
419
420 /**
421 * True if we need to hide the usual start side content in order to show the heads up
422 * notification info.
423 */
424 private val hideStartSideContentForHeadsUp: Flow<Boolean> =
425 if (StatusBarNoHunBehavior.isEnabled) {
426 flowOf(false)
427 } else {
<lambda>null428 headsUpNotificationInteractor.statusBarHeadsUpStatus.map {
429 it == PinnedStatus.PinnedBySystem
430 }
431 }
432
433 override val shouldShowOperatorNameView: Flow<Boolean> =
434 combine(
435 shouldHomeStatusBarBeVisible,
436 hideStartSideContentForHeadsUp,
437 homeStatusBarInteractor.visibilityViaDisableFlags,
438 homeStatusBarInteractor.shouldShowOperatorName,
439 ) {
shouldStatusBarBeVisiblenull440 shouldStatusBarBeVisible,
441 hideStartSideContentForHeadsUp,
442 visibilityViaDisableFlags,
443 shouldShowOperator ->
444 shouldStatusBarBeVisible &&
445 !hideStartSideContentForHeadsUp &&
446 visibilityViaDisableFlags.isSystemInfoAllowed &&
447 shouldShowOperator
448 }
449 .distinctUntilChanged()
450 .logDiffsForTable(
451 tableLogBuffer = tableLogger,
452 columnName = COL_SHOW_OPERATOR_NAME,
453 initialValue = false,
454 )
455 .flowOn(bgDispatcher)
456
457 override val canShowOngoingActivityChips: Flow<Boolean> =
458 combine(
459 isHomeStatusBarAllowed,
460 keyguardInteractor.isSecureCameraActive,
461 hideStartSideContentForHeadsUp,
462 ) { isHomeStatusBarAllowed, isSecureCameraActive, hideStartSideContentForHeadsUp ->
463 isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp
464 }
465
466 private val chipsVisibilityModel: Flow<ChipsVisibilityModel> =
chipsnull467 combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow
468 ->
469 ChipsVisibilityModel(chips, areChipsAllowed = canShow)
470 }
<lambda>null471 .traceEach(trackGroup(TRACK_GROUP, "chips"), logcat = true) {
472 "Chips[allowed=${it.areChipsAllowed} numChips=${it.chips.active.size}]"
473 }
474
475 override val ongoingActivityChips: ChipsVisibilityModel by
476 hydrator.hydratedStateOf(
477 traceName = "ongoingActivityChips",
478 initialValue =
479 ChipsVisibilityModel(
480 chips = MultipleOngoingActivityChipsModel(),
481 areChipsAllowed = false,
482 ),
483 source = chipsVisibilityModel,
484 )
485
486 private val hasOngoingActivityChips =
487 if (StatusBarChipsModernization.isEnabled) {
<lambda>null488 chipsVisibilityModel.map { it.chips.active.any { chip -> !chip.isHidden } }
489 } else if (StatusBarNotifChips.isEnabled) {
<lambda>null490 ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active }
491 } else {
<lambda>null492 primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Active }
493 }
494
495 private val isAnyChipVisible =
canShowChipsnull496 combine(hasOngoingActivityChips, canShowOngoingActivityChips) { hasChips, canShowChips ->
497 hasChips && canShowChips
498 }
499
500 override val isClockVisible: Flow<VisibilityModel> =
501 combine(
502 shouldHomeStatusBarBeVisible,
503 hideStartSideContentForHeadsUp,
504 homeStatusBarInteractor.visibilityViaDisableFlags,
visibilityViaDisableFlagsnull505 ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags
506 ->
507 val showClock =
508 shouldStatusBarBeVisible &&
509 visibilityViaDisableFlags.isClockAllowed &&
510 !hideStartSideContentForHeadsUp
511 // Always use View.INVISIBLE here, so that animations work
512 VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
513 }
514 .distinctUntilChanged()
515 .logDiffsForTable(
516 tableLogBuffer = tableLogger,
517 columnPrefix = COL_PREFIX_CLOCK,
518 initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
519 )
520 .flowOn(bgDispatcher)
521
522 override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
523 combine(
524 shouldHomeStatusBarBeVisible,
525 isAnyChipVisible,
526 homeStatusBarInteractor.visibilityViaDisableFlags,
visibilityViaDisableFlagsnull527 ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
528 val showNotificationIconContainer =
529 if (anyChipVisible) {
530 false
531 } else {
532 shouldStatusBarBeVisible &&
533 visibilityViaDisableFlags.areNotificationIconsAllowed
534 }
535 VisibilityModel(
536 showNotificationIconContainer.toVisibleOrGone(),
537 visibilityViaDisableFlags.animate,
538 )
539 }
540 .distinctUntilChanged()
541 .logDiffsForTable(
542 tableLogBuffer = tableLogger,
543 columnPrefix = COL_PREFIX_NOTIF_CONTAINER,
544 initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
545 )
546 .flowOn(bgDispatcher)
547
548 private val isSystemInfoVisible =
549 combine(shouldHomeStatusBarBeVisible, homeStatusBarInteractor.visibilityViaDisableFlags) {
shouldStatusBarBeVisiblenull550 shouldStatusBarBeVisible,
551 visibilityViaDisableFlags ->
552 val showSystemInfo =
553 shouldStatusBarBeVisible && visibilityViaDisableFlags.isSystemInfoAllowed
554 VisibilityModel(showSystemInfo.toVisibleOrGone(), visibilityViaDisableFlags.animate)
555 }
556
557 override val systemInfoCombinedVis =
558 combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState ->
559 SystemInfoCombinedVisibilityModel(sysInfoVisible, animationState)
560 }
561 .distinctUntilChanged()
562 .logDiffsForTable(
563 tableLogBuffer = tableLogger,
564 columnPrefix = COL_PREFIX_SYSTEM_INFO,
565 initialValue =
566 SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
567 )
568 .stateIn(
569 bgScope,
570 SharingStarted.WhileSubscribed(),
571 SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
572 )
573
574 override val iconBlockList: Flow<List<String>> =
575 homeStatusBarIconBlockListInteractor.iconBlockList.flowOn(bgDispatcher)
576
577 override val contentArea: Flow<Rect> =
578 statusBarContentInsetsViewModelStore.forDisplay(thisDisplayId)?.contentArea
579 ?: flowOf(Rect(0, 0, 0, 0)).flowOn(bgDispatcher)
580
581 @View.Visibility
toVisibleOrGonenull582 private fun Boolean.toVisibleOrGone(): Int {
583 return if (this) View.VISIBLE else View.GONE
584 }
585
586 // Similar to the above, but uses INVISIBLE in place of GONE
587 @View.Visibility
toVisibleOrInvisiblenull588 private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
589
590 override suspend fun onActivated(): Nothing {
591 coroutineScope {
592 launch { hydrator.activate() }
593 if (StatusBarPopupChips.isEnabled) {
594 launch { statusBarPopupChips.activate() }
595 }
596 launch { uiEventLogger.hydrateUiEventLogging(chipsFlow = chipsVisibilityModel) }
597 awaitCancellation()
598 }
599 }
600
601 /** Inject this to create the display-dependent view model */
602 @AssistedFactory
603 interface HomeStatusBarViewModelFactoryImpl :
604 HomeStatusBarViewModel.HomeStatusBarViewModelFactory {
createnull605 override fun create(displayId: Int): HomeStatusBarViewModelImpl
606 }
607
608 companion object {
609 private const val COL_LOCK_TO_OCCLUDED = "Lock->Occluded"
610 private const val COL_ALLOWED_BY_SCENE = "allowedByScene"
611 private const val COL_NOTIF_LIGHTS_OUT = "notifLightsOut"
612 private const val COL_SHOW_OPERATOR_NAME = "showOperatorName"
613 private const val COL_VISIBLE = "visible"
614 private const val COL_PREFIX_CLOCK = "clock"
615 private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer"
616 private const val COL_PREFIX_SYSTEM_INFO = "systemInfo"
617
618 private const val TRACK_GROUP = "StatusBar"
619
620 fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]"
621 }
622 }
623
624 /** Lookup the color for a given view in the status bar */
interfacenull625 fun interface StatusBarTintColor {
626 @ColorInt fun tint(viewBounds: Rect): Int
627 }
628