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.communal.domain.interactor 18 19 import android.content.pm.UserInfo 20 import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN 21 import com.android.systemui.communal.data.model.FEATURE_ENABLED 22 import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN 23 import com.android.systemui.communal.data.model.SuppressionReason 24 import com.android.systemui.communal.data.repository.CommunalSettingsRepository 25 import com.android.systemui.communal.shared.model.CommunalBackgroundType 26 import com.android.systemui.communal.shared.model.WhenToDream 27 import com.android.systemui.communal.shared.model.WhenToStartHub 28 import com.android.systemui.dagger.SysUISingleton 29 import com.android.systemui.dagger.qualifiers.Background 30 import com.android.systemui.settings.UserTracker 31 import com.android.systemui.user.domain.interactor.SelectedUserInteractor 32 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow 33 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated 34 import java.util.concurrent.Executor 35 import javax.inject.Inject 36 import kotlinx.coroutines.CoroutineDispatcher 37 import kotlinx.coroutines.CoroutineScope 38 import kotlinx.coroutines.channels.awaitClose 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.SharingStarted 41 import kotlinx.coroutines.flow.StateFlow 42 import kotlinx.coroutines.flow.flatMapLatest 43 import kotlinx.coroutines.flow.flowOf 44 import kotlinx.coroutines.flow.flowOn 45 import kotlinx.coroutines.flow.map 46 import kotlinx.coroutines.flow.stateIn 47 48 @SysUISingleton 49 class CommunalSettingsInteractor 50 @Inject 51 constructor( 52 @Background private val bgScope: CoroutineScope, 53 @Background private val bgDispatcher: CoroutineDispatcher, 54 @Background private val bgExecutor: Executor, 55 private val repository: CommunalSettingsRepository, 56 userInteractor: SelectedUserInteractor, 57 private val userTracker: UserTracker, 58 ) { 59 /** Whether communal is enabled at all. */ 60 val isCommunalEnabled: StateFlow<Boolean> = 61 repository 62 .isEnabled(FEATURE_ENABLED) 63 .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) 64 65 /** Whether manually opening the hub is enabled */ 66 val manualOpenEnabled: StateFlow<Boolean> = 67 repository 68 .isEnabled(FEATURE_MANUAL_OPEN) 69 .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) 70 71 /** Whether auto-opening the hub is enabled */ 72 val autoOpenEnabled: StateFlow<Boolean> = 73 repository 74 .isEnabled(FEATURE_AUTO_OPEN) 75 .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) 76 77 /** When to dream for the currently selected user. */ 78 val whenToDream: Flow<WhenToDream> = 79 userInteractor.selectedUserInfo.flatMapLatestConflated { user -> 80 repository.getWhenToDreamState(user) 81 } 82 83 /** When to automatically start hub for the currently selected user. */ 84 val whenToStartHub: Flow<WhenToStartHub> = 85 userInteractor.selectedUserInfo.flatMapLatest { user -> 86 repository.getWhenToStartHubState(user) 87 } 88 89 /** Whether communal hub is allowed by device policy for the current user */ 90 val allowedForCurrentUserByDevicePolicy: Flow<Boolean> = 91 userInteractor.selectedUserInfo.flatMapLatestConflated { user -> 92 repository.getAllowedByDevicePolicy(user) 93 } 94 95 /** Whether the hub is enabled for the current user */ 96 val settingEnabledForCurrentUser: Flow<Boolean> = 97 userInteractor.selectedUserInfo.flatMapLatestConflated { user -> 98 repository.getSettingEnabledByUser(user) 99 } 100 101 /** 102 * Returns true if any glanceable hub functionality should be enabled via configs and flags. 103 * 104 * This should be used for preventing basic glanceable hub functionality from running on devices 105 * that don't need it. 106 * 107 * If this is false, then the hub is definitely not available on the device. If this is true, 108 * refer to [isCommunalEnabled] which takes into account other factors that can change at 109 * runtime. 110 * 111 * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android 112 * config boolean. Otherwise, checks the old config_communalServiceEnabled config and 113 * communal_hub flag. 114 */ 115 fun isCommunalFlagEnabled(): Boolean = repository.getFlagEnabled() 116 117 /** 118 * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag 119 * are enabled. 120 * 121 * This should be used to flag off new glanceable hub or dream behavior that should launch 122 * together with the new hub experience that brings the hub to mobile. 123 * 124 * The trunk-stable flag is controlled by server rollout and is on all devices. The Android 125 * config flag is enabled via resource overlay only on products we want the hub to be present 126 * on. 127 */ 128 fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled() 129 130 /** 131 * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be 132 * suppressed. 133 */ 134 fun setSuppressionReasons(reasons: List<SuppressionReason>) { 135 repository.setSuppressionReasons(reasons) 136 } 137 138 /** The type of background to use for the hub. Used to experiment with different backgrounds */ 139 val communalBackground: Flow<CommunalBackgroundType> = 140 userInteractor.selectedUserInfo 141 .flatMapLatest { user -> repository.getBackground(user) } 142 .flowOn(bgDispatcher) 143 144 private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow { 145 fun send(profiles: List<UserInfo>) { 146 trySend(profiles.find { it.isManagedProfile }) 147 } 148 149 val callback = 150 object : UserTracker.Callback { 151 override fun onProfilesChanged(profiles: List<UserInfo>) { 152 send(profiles) 153 } 154 } 155 userTracker.addCallback(callback, bgExecutor) 156 send(userTracker.userProfiles) 157 158 awaitClose { userTracker.removeCallback(callback) } 159 } 160 161 /** 162 * A user that device policy says shouldn't allow communal widgets, or null if there are no 163 * restrictions. 164 */ 165 val workProfileUserDisallowedByDevicePolicy: StateFlow<UserInfo?> = 166 workProfileUserInfoCallbackFlow 167 .flatMapLatest { workProfile -> 168 workProfile?.let { 169 repository.getAllowedByDevicePolicy(it).map { allowed -> 170 if (!allowed) it else null 171 } 172 } ?: flowOf(null) 173 } 174 .stateIn( 175 scope = bgScope, 176 started = SharingStarted.WhileSubscribed(), 177 initialValue = null, 178 ) 179 } 180