1 /*
<lambda>null2  * Copyright (C) 2024 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.data
18 
19 import android.os.Bundle
20 import androidx.annotation.VisibleForTesting
21 import com.android.systemui.dagger.SysUISingleton
22 import com.android.systemui.dagger.qualifiers.Application
23 import com.android.systemui.demomode.DemoMode
24 import com.android.systemui.demomode.DemoModeController
25 import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository
26 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
27 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
28 import javax.inject.Inject
29 import kotlinx.coroutines.CoroutineScope
30 import kotlinx.coroutines.ExperimentalCoroutinesApi
31 import kotlinx.coroutines.channels.awaitClose
32 import kotlinx.coroutines.flow.SharingStarted
33 import kotlinx.coroutines.flow.StateFlow
34 import kotlinx.coroutines.flow.flatMapLatest
35 import kotlinx.coroutines.flow.mapLatest
36 import kotlinx.coroutines.flow.stateIn
37 
38 /**
39  * A provider for the [DeviceBasedSatelliteRepository] interface that can choose between the Demo
40  * and Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo],
41  * which switches based on the latest information from [DemoModeController], and switches every flow
42  * in the interface to point to the currently-active provider. This allows us to put the demo mode
43  * interface in its own repository, completely separate from the real version, while still using all
44  * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
45  * something like this:
46  * ```
47  * RealRepository
48  *                 │
49  *                 ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
50  *                 │
51  * DemoRepository
52  * ```
53  */
54 @OptIn(ExperimentalCoroutinesApi::class)
55 @SysUISingleton
56 class DeviceBasedSatelliteRepositorySwitcher
57 @Inject
58 constructor(
59     private val realImpl: RealDeviceBasedSatelliteRepository,
60     private val demoImpl: DemoDeviceBasedSatelliteRepository,
61     private val demoModeController: DemoModeController,
62     @Application scope: CoroutineScope,
63 ) : DeviceBasedSatelliteRepository {
64     private val isDemoMode =
65         conflatedCallbackFlow {
66                 val callback =
67                     object : DemoMode {
68                         override fun dispatchDemoCommand(command: String?, args: Bundle?) {
69                             // Don't care
70                         }
71 
72                         override fun onDemoModeStarted() {
73                             demoImpl.startProcessingCommands()
74                             trySend(true)
75                         }
76 
77                         override fun onDemoModeFinished() {
78                             demoImpl.stopProcessingCommands()
79                             trySend(false)
80                         }
81                     }
82 
83                 demoModeController.addCallback(callback)
84                 awaitClose { demoModeController.removeCallback(callback) }
85             }
86             .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
87 
88     @VisibleForTesting
89     val activeRepo: StateFlow<DeviceBasedSatelliteRepository> =
90         isDemoMode
91             .mapLatest { isDemoMode ->
92                 if (isDemoMode) {
93                     demoImpl
94                 } else {
95                     realImpl
96                 }
97             }
98             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
99 
100     override val isSatelliteProvisioned: StateFlow<Boolean> =
101         activeRepo
102             .flatMapLatest { it.isSatelliteProvisioned }
103             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isSatelliteProvisioned.value)
104 
105     override val connectionState: StateFlow<SatelliteConnectionState> =
106         activeRepo
107             .flatMapLatest { it.connectionState }
108             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.connectionState.value)
109 
110     override val signalStrength: StateFlow<Int> =
111         activeRepo
112             .flatMapLatest { it.signalStrength }
113             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.signalStrength.value)
114 
115     override val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean> =
116         activeRepo
117             .flatMapLatest { it.isSatelliteAllowedForCurrentLocation }
118             .stateIn(
119                 scope,
120                 SharingStarted.WhileSubscribed(),
121                 realImpl.isSatelliteAllowedForCurrentLocation.value
122             )
123 }
124