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