• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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