• 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.satellite.ui.viewmodel
18 
19 import android.content.Context
20 import com.android.systemui.common.shared.model.Icon
21 import com.android.systemui.dagger.SysUISingleton
22 import com.android.systemui.dagger.qualifiers.Application
23 import com.android.systemui.log.LogBuffer
24 import com.android.systemui.log.core.LogLevel
25 import com.android.systemui.log.table.TableLogBuffer
26 import com.android.systemui.log.table.logDiffsForTable
27 import com.android.systemui.res.R
28 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
29 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
30 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteTableLog
31 import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
32 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
33 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
34 import javax.inject.Inject
35 import kotlin.time.Duration.Companion.seconds
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.delay
38 import kotlinx.coroutines.flow.SharingStarted
39 import kotlinx.coroutines.flow.StateFlow
40 import kotlinx.coroutines.flow.combine
41 import kotlinx.coroutines.flow.distinctUntilChanged
42 import kotlinx.coroutines.flow.flatMapLatest
43 import kotlinx.coroutines.flow.flowOf
44 import kotlinx.coroutines.flow.stateIn
45 
46 /**
47  * View-Model for the device-based satellite icon. This icon will only show in the status bar if
48  * satellite is available AND all other service states are considered OOS.
49  */
50 interface DeviceBasedSatelliteViewModel {
51     /**
52      * The satellite icon that should be displayed, or null if no satellite icon should be
53      * displayed.
54      */
55     val icon: StateFlow<Icon?>
56 
57     /**
58      * The satellite-related text that should be used as the carrier text string when satellite is
59      * active, or null if the carrier text string shouldn't include any satellite information.
60      */
61     val carrierText: StateFlow<String?>
62 }
63 
64 @SysUISingleton
65 class DeviceBasedSatelliteViewModelImpl
66 @Inject
67 constructor(
68     context: Context,
69     interactor: DeviceBasedSatelliteInteractor,
70     @Application scope: CoroutineScope,
71     airplaneModeRepository: AirplaneModeRepository,
72     @DeviceBasedSatelliteInputLog logBuffer: LogBuffer,
73     @DeviceBasedSatelliteTableLog tableLog: TableLogBuffer,
74 ) : DeviceBasedSatelliteViewModel {
75 
76     // This adds a 10 seconds delay before showing the icon
77     private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
78         if (interactor.isOpportunisticSatelliteIconEnabled) {
79                 interactor.areAllConnectionsOutOfService
shouldShownull80                     .flatMapLatest { shouldShow ->
81                         if (shouldShow) {
82                             logBuffer.log(
83                                 TAG,
84                                 LogLevel.INFO,
85                                 { long1 = DELAY_DURATION.inWholeSeconds },
86                                 { "Waiting $long1 seconds before showing the satellite icon" },
87                             )
88                             delay(DELAY_DURATION)
89                             flowOf(true)
90                         } else {
91                             flowOf(false)
92                         }
93                     }
94                     .distinctUntilChanged()
95                     .logDiffsForTable(
96                         tableLog,
97                         columnPrefix = "vm",
98                         columnName = COL_VISIBLE_FOR_OOS,
99                         initialValue = false,
100                     )
101             } else {
102                 flowOf(false)
103             }
104             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
105 
106     private val canShowIcon =
107         combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
allowednull108             allowed,
109             provisioned ->
110             allowed && provisioned
111         }
112 
113     private val showIcon =
114         if (interactor.isOpportunisticSatelliteIconEnabled) {
115                 canShowIcon
116                     .flatMapLatest { canShow ->
117                         if (!canShow) {
118                             flowOf(false)
119                         } else {
120                             combine(
121                                 shouldShowIconForOosAfterHysteresis,
122                                 interactor.isAnyConnectionNtn,
123                                 interactor.connectionState,
124                                 interactor.isWifiActive,
125                                 airplaneModeRepository.isAirplaneMode,
126                             ) { showForOos, anyNtn, connectionState, isWifiActive, isAirplaneMode ->
127                                 // anyNtn means that there is some mobile network using ntn, and the
128                                 // mobile icon will show its own satellite icon
129                                 if (isWifiActive || isAirplaneMode || anyNtn) {
130                                     false
131                                 } else {
132                                     // Show for out of service (which has a hysteresis), or ignore
133                                     // the hysteresis if we're already connected
134                                     showForOos ||
135                                         connectionState == SatelliteConnectionState.On ||
136                                         connectionState == SatelliteConnectionState.Connected
137                                 }
138                             }
139                         }
140                     }
141                     .distinctUntilChanged()
142                     .logDiffsForTable(
143                         tableLog,
144                         columnPrefix = "vm",
145                         columnName = COL_VISIBLE,
146                         initialValue = false,
147                     )
148             } else {
149                 flowOf(false)
150             }
151             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
152 
153     override val icon: StateFlow<Icon?> =
154         combine(showIcon, interactor.connectionState, interactor.signalStrength) {
shouldShownull155                 shouldShow,
156                 state,
157                 signalStrength ->
158                 if (shouldShow) {
159                     SatelliteIconModel.fromConnectionState(state, signalStrength)
160                 } else {
161                     null
162                 }
163             }
164             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
165 
166     override val carrierText: StateFlow<String?> =
connectionStatenull167         combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
168                 logBuffer.log(
169                     TAG,
170                     LogLevel.INFO,
171                     {
172                         bool1 = shouldShow
173                         str1 = connectionState.name
174                     },
175                     { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
176                 )
177                 if (shouldShow) {
178                     when (connectionState) {
179                         SatelliteConnectionState.On,
180                         SatelliteConnectionState.Connected ->
181                             context.getString(R.string.satellite_connected_carrier_text)
182                         SatelliteConnectionState.Off,
183                         SatelliteConnectionState.Unknown -> {
184                             // If we're showing the satellite icon opportunistically, use the
185                             // emergency-only version of the carrier string
186                             context.getString(R.string.satellite_emergency_only_carrier_text)
187                         }
188                     }
189                 } else {
190                     null
191                 }
192             }
193             .distinctUntilChanged()
194             .logDiffsForTable(
195                 tableLog,
196                 columnPrefix = "vm",
197                 columnName = COL_CARRIER_TEXT,
198                 initialValue = null,
199             )
200             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
201 
202     companion object {
203         private const val TAG = "DeviceBasedSatelliteViewModel"
204         private val DELAY_DURATION = 10.seconds
205 
206         const val COL_VISIBLE_FOR_OOS = "visibleForOos"
207         const val COL_VISIBLE = "visible"
208         const val COL_CARRIER_TEXT = "carrierText"
209     }
210 }
211