• 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 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
17 
18 import android.content.res.Resources
19 import android.graphics.Rect
20 import com.android.systemui.dagger.qualifiers.Background
21 import com.android.systemui.dagger.qualifiers.Main
22 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
23 import com.android.systemui.plugins.DarkIconDispatcher
24 import com.android.systemui.res.R
25 import com.android.systemui.shade.domain.interactor.ShadeInteractor
26 import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
27 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
28 import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
29 import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
30 import com.android.systemui.util.kotlin.pairwise
31 import com.android.systemui.util.kotlin.sample
32 import com.android.systemui.util.ui.AnimatableEvent
33 import com.android.systemui.util.ui.AnimatedValue
34 import com.android.systemui.util.ui.toAnimatedValueFlow
35 import javax.inject.Inject
36 import kotlin.coroutines.CoroutineContext
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.flow.conflate
40 import kotlinx.coroutines.flow.distinctUntilChanged
41 import kotlinx.coroutines.flow.emptyFlow
42 import kotlinx.coroutines.flow.filterNotNull
43 import kotlinx.coroutines.flow.flowOf
44 import kotlinx.coroutines.flow.flowOn
45 import kotlinx.coroutines.flow.map
46 
47 /** View-model for the row of notification icons displayed in the status bar, */
48 class NotificationIconContainerStatusBarViewModel
49 @Inject
50 constructor(
51     @Background private val bgContext: CoroutineContext,
52     private val darkIconInteractor: DarkIconInteractor,
53     iconsInteractor: StatusBarNotificationIconsInteractor,
54     headsUpIconInteractor: HeadsUpNotificationIconInteractor,
55     keyguardInteractor: KeyguardInteractor,
56     @Main resources: Resources,
57     shadeInteractor: ShadeInteractor,
58 ) {
59 
60     private val maxIcons = resources.getInteger(R.integer.max_notif_static_icons)
61 
62     /** Are changes to the icon container animated? */
63     val animationsEnabled: Flow<Boolean> =
64         combine(shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardShowing) {
65                 panelTouchesEnabled,
66                 isKeyguardShowing ->
67                 panelTouchesEnabled && !isKeyguardShowing
68             }
69             .flowOn(bgContext)
70             .conflate()
71             .distinctUntilChanged()
72 
73     /** The colors with which to display the notification icons. */
74     fun iconColors(displayId: Int): Flow<NotificationIconColors> {
75         return darkIconInteractor
76             .darkState(displayId)
77             .map { (areas: Collection<Rect>, tint: Int) -> IconColorsImpl(tint, areas) }
78             .flowOn(bgContext)
79             .conflate()
80             .distinctUntilChanged()
81     }
82 
83     /** [NotificationIconsViewData] indicating which icons to display in the view. */
84     val icons: Flow<NotificationIconsViewData> =
85         iconsInteractor.statusBarNotifs
86             .map { entries ->
87                 NotificationIconsViewData(
88                     visibleIcons = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
89                     iconLimit = maxIcons,
90                 )
91             }
92             .flowOn(bgContext)
93             .conflate()
94             .distinctUntilChanged()
95 
96     /** An Icon to show "isolated" in the IconContainer. */
97     val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
98         if (StatusBarNoHunBehavior.isEnabled) {
99             flowOf(AnimatedValue.NotAnimating(null))
100         } else {
101             headsUpIconInteractor.isolatedNotification
102                 .combine(icons) { isolatedNotif, iconsViewData ->
103                     isolatedNotif?.let {
104                         iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
105                     }
106                 }
107                 .distinctUntilChanged()
108                 .flowOn(bgContext)
109                 .conflate()
110                 .distinctUntilChanged()
111                 .pairwise(initialValue = null)
112                 .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
113                     val animate =
114                         when {
115                             iconInfo?.notifKey == prev?.notifKey -> false
116                             iconInfo == null || prev == null -> shadeExpansion == 0f
117                             else -> false
118                         }
119                     AnimatableEvent(iconInfo, animate)
120                 }
121                 .toAnimatedValueFlow()
122         }
123 
124     /** Location to show an isolated icon, if there is one. */
125     val isolatedIconLocation: Flow<Rect> =
126         if (StatusBarNoHunBehavior.isEnabled) {
127             emptyFlow()
128         } else {
129             headsUpIconInteractor.isolatedIconLocation
130                 .filterNotNull()
131                 .conflate()
132                 .distinctUntilChanged()
133         }
134 
135     private class IconColorsImpl(override val tint: Int, private val areas: Collection<Rect>) :
136         NotificationIconColors {
137         override fun staticDrawableColor(viewBounds: Rect): Int {
138             return if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
139                 tint
140             } else {
141                 DarkIconDispatcher.DEFAULT_ICON_TINT
142             }
143         }
144     }
145 }
146