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