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