• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.mobile.ui.viewmodel
18 
19 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
20 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
21 import com.android.settingslib.graph.SignalDrawable
22 import com.android.systemui.common.shared.model.ContentDescription
23 import com.android.systemui.common.shared.model.Icon
24 import com.android.systemui.log.table.logDiffsForTable
25 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
26 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
27 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
28 import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
29 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
30 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.ExperimentalCoroutinesApi
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.SharingStarted
35 import kotlinx.coroutines.flow.StateFlow
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.flow.distinctUntilChanged
38 import kotlinx.coroutines.flow.flowOf
39 import kotlinx.coroutines.flow.map
40 import kotlinx.coroutines.flow.mapLatest
41 import kotlinx.coroutines.flow.stateIn
42 
43 /** Common interface for all of the location-based mobile icon view models. */
44 interface MobileIconViewModelCommon {
45     val subscriptionId: Int
46     /** True if this view should be visible at all. */
47     val isVisible: StateFlow<Boolean>
48     val icon: Flow<SignalIconModel>
49     val contentDescription: Flow<ContentDescription>
50     val roaming: Flow<Boolean>
51     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
52     val networkTypeIcon: Flow<Icon.Resource?>
53     val activityInVisible: Flow<Boolean>
54     val activityOutVisible: Flow<Boolean>
55     val activityContainerVisible: Flow<Boolean>
56 }
57 
58 /**
59  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
60  * a single line of service via [MobileIconInteractor] and update the UI based on that
61  * subscription's information.
62  *
63  * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
64  * [MobileIconsInteractor.filteredSubscriptions].
65  *
66  * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
67  * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
68  * model gets the exact same information, as well as allows us to log that unified state only once
69  * per icon.
70  */
71 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
72 @OptIn(ExperimentalCoroutinesApi::class)
73 class MobileIconViewModel
74 constructor(
75     override val subscriptionId: Int,
76     iconInteractor: MobileIconInteractor,
77     airplaneModeInteractor: AirplaneModeInteractor,
78     constants: ConnectivityConstants,
79     scope: CoroutineScope,
80 ) : MobileIconViewModelCommon {
81     /** Whether or not to show the error state of [SignalDrawable] */
82     private val showExclamationMark: Flow<Boolean> =
<lambda>null83         iconInteractor.isDefaultDataEnabled.mapLatest { !it }
84 
85     override val isVisible: StateFlow<Boolean> =
86         if (!constants.hasDataCapabilities) {
87                 flowOf(false)
88             } else {
89                 combine(
90                     airplaneModeInteractor.isAirplaneMode,
91                     iconInteractor.isForceHidden,
isForceHiddennull92                 ) { isAirplaneMode, isForceHidden ->
93                     !isAirplaneMode && !isForceHidden
94                 }
95             }
96             .distinctUntilChanged()
97             .logDiffsForTable(
98                 iconInteractor.tableLogBuffer,
99                 columnPrefix = "",
100                 columnName = "visible",
101                 initialValue = false,
102             )
103             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
104 
<lambda>null105     override val icon: Flow<SignalIconModel> = run {
106         val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value)
107         combine(
108                 iconInteractor.level,
109                 iconInteractor.numberOfLevels,
110                 showExclamationMark,
111                 iconInteractor.isInService,
112             ) { level, numberOfLevels, showExclamationMark, isInService ->
113                 if (!isInService) {
114                     SignalIconModel.createEmptyState(numberOfLevels)
115                 } else {
116                     SignalIconModel(level, numberOfLevels, showExclamationMark)
117                 }
118             }
119             .distinctUntilChanged()
120             .logDiffsForTable(
121                 iconInteractor.tableLogBuffer,
122                 columnPrefix = "icon",
123                 initialValue = initial,
124             )
125             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
126     }
127 
<lambda>null128     override val contentDescription: Flow<ContentDescription> = run {
129         val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH_NONE)
130         combine(
131                 iconInteractor.level,
132                 iconInteractor.isInService,
133             ) { level, isInService ->
134                 val resId =
135                     when {
136                         isInService -> PHONE_SIGNAL_STRENGTH[level]
137                         else -> PHONE_SIGNAL_STRENGTH_NONE
138                     }
139                 ContentDescription.Resource(resId)
140             }
141             .distinctUntilChanged()
142             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
143     }
144 
145     private val showNetworkTypeIcon: Flow<Boolean> =
146         combine(
147                 iconInteractor.isDataConnected,
148                 iconInteractor.isDataEnabled,
149                 iconInteractor.isDefaultConnectionFailed,
150                 iconInteractor.alwaysShowDataRatIcon,
151                 iconInteractor.isConnected,
dataConnectednull152             ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
153                 alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
154             }
155             .distinctUntilChanged()
156             .logDiffsForTable(
157                 iconInteractor.tableLogBuffer,
158                 columnPrefix = "",
159                 columnName = "showNetworkTypeIcon",
160                 initialValue = false,
161             )
162             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
163 
164     override val networkTypeIcon: Flow<Icon.Resource?> =
165         combine(
166                 iconInteractor.networkTypeIconGroup,
167                 showNetworkTypeIcon,
networkTypeIconGroupnull168             ) { networkTypeIconGroup, shouldShow ->
169                 val desc =
170                     if (networkTypeIconGroup.dataContentDescription != 0)
171                         ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
172                     else null
173                 val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
174                 return@combine when {
175                     !shouldShow -> null
176                     else -> icon
177                 }
178             }
179             .distinctUntilChanged()
180             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
181 
182     override val roaming: StateFlow<Boolean> =
183         iconInteractor.isRoaming
184             .logDiffsForTable(
185                 iconInteractor.tableLogBuffer,
186                 columnPrefix = "",
187                 columnName = "roaming",
188                 initialValue = false,
189             )
190             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
191 
192     private val activity: Flow<DataActivityModel?> =
193         if (!constants.shouldShowActivityConfig) {
194             flowOf(null)
195         } else {
196             iconInteractor.activity
197         }
198 
199     override val activityInVisible: Flow<Boolean> =
200         activity
<lambda>null201             .map { it?.hasActivityIn ?: false }
202             .distinctUntilChanged()
203             .logDiffsForTable(
204                 iconInteractor.tableLogBuffer,
205                 columnPrefix = "",
206                 columnName = "activityInVisible",
207                 initialValue = false,
208             )
209             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
210 
211     override val activityOutVisible: Flow<Boolean> =
212         activity
<lambda>null213             .map { it?.hasActivityOut ?: false }
214             .distinctUntilChanged()
215             .logDiffsForTable(
216                 iconInteractor.tableLogBuffer,
217                 columnPrefix = "",
218                 columnName = "activityOutVisible",
219                 initialValue = false,
220             )
221             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
222 
223     override val activityContainerVisible: Flow<Boolean> =
224         activity
<lambda>null225             .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
226             .distinctUntilChanged()
227             .logDiffsForTable(
228                 iconInteractor.tableLogBuffer,
229                 columnPrefix = "",
230                 columnName = "activityContainerVisible",
231                 initialValue = false,
232             )
233             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
234 }
235