• 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.domain.interactor
18 
19 import com.android.systemui.dagger.SysUISingleton
20 import com.android.systemui.dagger.qualifiers.Background
21 import com.android.systemui.log.LogBuffer
22 import com.android.systemui.log.core.LogLevel
23 import com.android.systemui.log.table.TableLogBuffer
24 import com.android.systemui.log.table.logDiffsForTable
25 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
26 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteTableLog
27 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
28 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
29 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
30 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
31 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
32 import javax.inject.Inject
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.flow.distinctUntilChanged
38 import kotlinx.coroutines.flow.flatMapLatest
39 import kotlinx.coroutines.flow.flowOf
40 import kotlinx.coroutines.flow.map
41 import kotlinx.coroutines.flow.stateIn
42 
43 @SysUISingleton
44 class DeviceBasedSatelliteInteractor
45 @Inject
46 constructor(
47     val repo: DeviceBasedSatelliteRepository,
48     iconsInteractor: MobileIconsInteractor,
49     wifiInteractor: WifiInteractor,
50     @Background scope: CoroutineScope,
51     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
52     @DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
53 ) {
54     /** Whether or not we should show the satellite icon when all connections are OOS */
55     val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
56 
57     /** Must be observed by any UI showing Satellite iconography */
58     val isSatelliteAllowed =
59         repo.isSatelliteAllowedForCurrentLocation
60             .logDiffsForTable(tableLog, columnName = COL_ALLOWED, initialValue = false)
61             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
62 
63     /** See [SatelliteConnectionState] for relevant states */
64     val connectionState =
65         repo.connectionState
66             .logDiffsForTable(tableLog, initialValue = SatelliteConnectionState.Off)
67             .stateIn(scope, SharingStarted.WhileSubscribed(), SatelliteConnectionState.Off)
68 
69     /** 0-4 description of the connection strength */
70     val signalStrength =
71         repo.signalStrength
72             .logDiffsForTable(tableLog, columnName = COL_LEVEL, initialValue = 0)
73             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
74 
75     val isSatelliteProvisioned = repo.isSatelliteProvisioned
76 
77     val isWifiActive: Flow<Boolean> =
78         wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
79 
80     private val allConnectionsOos =
81         iconsInteractor.icons
82             .aggregateOver(
83                 selector = { intr ->
84                     combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
85                         isInService,
86                         isEmergencyOnly,
87                         isNtn ->
88                         !isInService && !isEmergencyOnly && !isNtn
89                     }
90                 },
91                 defaultValue = true, // no connections == everything is OOS
92             ) { isOosAndNotEmergencyAndNotSatellite ->
93                 isOosAndNotEmergencyAndNotSatellite.all { it }
94             }
95             .distinctUntilChanged()
96             .logDiffsForTable(tableLog, columnName = COL_ALL_OOS, initialValue = true)
97 
98     /** When all connections are considered OOS, satellite connectivity is potentially valid */
99     val areAllConnectionsOutOfService =
100         combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
101                 connectionsOos,
102                 deviceEmergencyOnly ->
103                 logBuffer.log(
104                     TAG,
105                     LogLevel.INFO,
106                     {
107                         bool1 = connectionsOos
108                         bool2 = deviceEmergencyOnly
109                     },
110                     {
111                         "Updating OOS status. allConnectionsOOs=$bool1 " +
112                             "deviceEmergencyOnly=$bool2"
113                     },
114                 )
115                 // If no connections exist, or all are OOS, then we look to the device-based
116                 // service state to detect if any calls are possible
117                 connectionsOos && !deviceEmergencyOnly
118             }
119             .distinctUntilChanged()
120             .logDiffsForTable(tableLog, columnName = COL_FULL_OOS, initialValue = true)
121             .stateIn(scope, SharingStarted.WhileSubscribed(), true)
122 
123     /** True if any known mobile network is currently using a non terrestrial network */
124     val isAnyConnectionNtn =
125         iconsInteractor.icons.aggregateOver(selector = { it.isNonTerrestrial }, false) {
126             nonTerrestrialNetworks ->
127             nonTerrestrialNetworks.any { it == true }
128         }
129 
130     companion object {
131         const val TAG = "DeviceBasedSatelliteInteractor"
132 
133         const val COL_LEVEL = "level"
134         const val COL_ALL_OOS = "allConnsOOS"
135         const val COL_ALLOWED = "allowed"
136         // Going to try to optimize for not using too much width on the table here. This information
137         // can be ascertained by checking for the device emergency only in the mobile logs as well
138         const val COL_FULL_OOS = "allOosAndNoEmer"
139     }
140 }
141 
142 /**
143  * aggregateOver allows us to combine over the leaf-nodes of successive lists emitted from the
144  * top-level flow. Re-emits if the list changes, or any of the intermediate values change.
145  *
146  * Provides a way to connect the reactivity of the top-level flow with the reactivity of an
147  * arbitrarily-defined relationship ([selector]) from R to the flow that R exposes.
148  *
149  * [defaultValue] allows for a default value to be used if there are no leaf nodes after applying
150  * [selector]. E.g., if there are no mobile connections, assume that there is no service.
151  */
aggregateOvernull152 private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver(
153     crossinline selector: (R) -> Flow<S>,
154     defaultValue: T,
155     crossinline transform: (Array<S>) -> T,
156 ): Flow<T> {
157     return map { list -> list.map { selector(it) } }
158         .flatMapLatest { newFlows ->
159             if (newFlows.isEmpty()) {
160                 flowOf(defaultValue)
161             } else {
162                 combine(newFlows) { newVals -> transform(newVals) }
163             }
164         }
165 }
166