• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.mobile.data.repository
18 
19 import android.os.Bundle
20 import androidx.annotation.VisibleForTesting
21 import com.android.settingslib.SignalIcon
22 import com.android.settingslib.mobile.MobileMappings
23 import com.android.systemui.Flags
24 import com.android.systemui.KairosActivatable
25 import com.android.systemui.KairosBuilder
26 import com.android.systemui.activated
27 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
28 import com.android.systemui.dagger.SysUISingleton
29 import com.android.systemui.demomode.DemoMode
30 import com.android.systemui.demomode.DemoModeController
31 import com.android.systemui.kairos.Events
32 import com.android.systemui.kairos.ExperimentalKairosApi
33 import com.android.systemui.kairos.Incremental
34 import com.android.systemui.kairos.State
35 import com.android.systemui.kairos.flatMap
36 import com.android.systemui.kairos.map
37 import com.android.systemui.kairos.switchEvents
38 import com.android.systemui.kairos.switchIncremental
39 import com.android.systemui.kairosBuilder
40 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
41 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
42 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
43 import dagger.Binds
44 import dagger.Provides
45 import dagger.multibindings.ElementsIntoSet
46 import javax.inject.Inject
47 import javax.inject.Provider
48 import kotlinx.coroutines.channels.awaitClose
49 
50 /**
51  * A provider for the [MobileConnectionsRepository] interface that can choose between the Demo and
52  * Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], which
53  * switches based on the latest information from [DemoModeController], and switches every flow in
54  * the interface to point to the currently-active provider. This allows us to put the demo mode
55  * interface in its own repository, completely separate from the real version, while still using all
56  * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
57  * something like this:
58  * ```
59  * RealRepository
60  *       │
61  *       ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
62  *       │
63  * DemoRepository
64  * ```
65  *
66  * NOTE: because the UI layer for mobile icons relies on a nested-repository structure, it is likely
67  * that we will have to drain the subscription list whenever demo mode changes. Otherwise if a real
68  * subscription list [1] is replaced with a demo subscription list [1], the view models will not see
69  * a change (due to `distinctUntilChanged`) and will not refresh their data providers to the demo
70  * implementation.
71  */
72 @ExperimentalKairosApi
73 @SysUISingleton
74 class MobileRepositorySwitcherKairos
75 @Inject
76 constructor(
77     private val realRepository: MobileConnectionsRepositoryKairosImpl,
78     private val demoRepositoryFactory: DemoMobileConnectionsRepositoryKairos.Factory,
79     demoModeController: DemoModeController,
80 ) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
81 
82     private val isDemoMode: State<Boolean> = buildState {
83         conflatedCallbackFlow {
84                 val callback =
85                     object : DemoMode {
86                         override fun dispatchDemoCommand(command: String?, args: Bundle?) {
87                             // Nothing, we just care about on/off
88                         }
89 
90                         override fun onDemoModeStarted() {
91                             trySend(true)
92                         }
93 
94                         override fun onDemoModeFinished() {
95                             trySend(false)
96                         }
97                     }
98 
99                 demoModeController.addCallback(callback)
100                 awaitClose { demoModeController.removeCallback(callback) }
101             }
102             .toState(demoModeController.isInDemoMode)
103     }
104 
105     // Convenient definition flow for the currently active repo (based on demo mode or not)
106     @VisibleForTesting
107     val activeRepo: State<MobileConnectionsRepositoryKairos> = buildState {
108         isDemoMode.mapLatestBuild { demoMode ->
109             if (demoMode) {
110                 activated { demoRepositoryFactory.create() }
111             } else {
112                 realRepository
113             }
114         }
115     }
116 
117     override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
118         activeRepo.map { it.mobileConnectionsBySubId }.switchIncremental()
119 
120     override val subscriptions: State<Collection<SubscriptionModel>> =
121         activeRepo.flatMap { it.subscriptions }
122 
123     override val activeMobileDataSubscriptionId: State<Int?> =
124         activeRepo.flatMap { it.activeMobileDataSubscriptionId }
125 
126     override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
127         activeRepo.flatMap { it.activeMobileDataRepository }
128 
129     override val activeSubChangedInGroupEvent: Events<Unit> =
130         activeRepo.map { it.activeSubChangedInGroupEvent }.switchEvents()
131 
132     override val defaultDataSubRatConfig: State<MobileMappings.Config> =
133         activeRepo.flatMap { it.defaultDataSubRatConfig }
134 
135     override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
136         activeRepo.flatMap { it.defaultMobileIconMapping }
137 
138     override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> =
139         activeRepo.flatMap { it.defaultMobileIconGroup }
140 
141     override val isDeviceEmergencyCallCapable: State<Boolean> =
142         activeRepo.flatMap { it.isDeviceEmergencyCallCapable }
143 
144     override val isAnySimSecure: State<Boolean> = activeRepo.flatMap { it.isAnySimSecure }
145 
146     override val defaultDataSubId: State<Int?> = activeRepo.flatMap { it.defaultDataSubId }
147 
148     override val mobileIsDefault: State<Boolean> = activeRepo.flatMap { it.mobileIsDefault }
149 
150     override val hasCarrierMergedConnection: State<Boolean> =
151         activeRepo.flatMap { it.hasCarrierMergedConnection }
152 
153     override val defaultConnectionIsValidated: State<Boolean> =
154         activeRepo.flatMap { it.defaultConnectionIsValidated }
155 
156     override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
157 
158     @dagger.Module
159     interface Module {
160         @Binds fun bindImpl(impl: MobileRepositorySwitcherKairos): MobileConnectionsRepositoryKairos
161 
162         companion object {
163             @Provides
164             @ElementsIntoSet
165             fun kairosActivatable(
166                 impl: Provider<MobileRepositorySwitcherKairos>
167             ): Set<@JvmSuppressWildcards KairosActivatable> =
168                 if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
169         }
170     }
171 }
172