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