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.data.repository
18
19 import android.app.admin.DevicePolicyManager
20 import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
21 import android.content.IntentFilter
22 import android.content.pm.UserInfo
23 import android.content.res.Resources
24 import android.os.UserHandle
25 import android.provider.Settings
26 import com.android.systemui.Flags.communalHub
27 import com.android.systemui.Flags.glanceableHubV2
28 import com.android.systemui.broadcast.BroadcastDispatcher
29 import com.android.systemui.communal.data.model.CommunalFeature
30 import com.android.systemui.communal.data.model.FEATURE_ALL
31 import com.android.systemui.communal.data.model.SuppressionReason
32 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE
33 import com.android.systemui.communal.shared.model.CommunalBackgroundType
34 import com.android.systemui.communal.shared.model.WhenToDream
35 import com.android.systemui.communal.shared.model.WhenToStartHub
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Background
38 import com.android.systemui.dagger.qualifiers.Main
39 import com.android.systemui.flags.FeatureFlagsClassic
40 import com.android.systemui.flags.Flags
41 import com.android.systemui.util.kotlin.emitOnStart
42 import com.android.systemui.util.settings.SecureSettings
43 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
44 import javax.inject.Inject
45 import javax.inject.Named
46 import kotlinx.coroutines.CoroutineDispatcher
47 import kotlinx.coroutines.flow.Flow
48 import kotlinx.coroutines.flow.MutableStateFlow
49 import kotlinx.coroutines.flow.flowOn
50 import kotlinx.coroutines.flow.map
51
52 interface CommunalSettingsRepository {
53 /** Whether a particular feature is enabled */
54 fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean>
55
56 /**
57 * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
58 * suppressed.
59 */
60 fun setSuppressionReasons(reasons: List<SuppressionReason>)
61
62 /**
63 * Returns a [WhenToDream] for the specified user, indicating what state the device should be in
64 * to trigger dreams.
65 */
66 fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream>
67
68 /**
69 * Returns a[WhenToStartHub] for the specified user, indicating what state the device should be
70 * in to automatically display the hub.
71 */
72 fun getWhenToStartHubState(user: UserInfo): Flow<WhenToStartHub>
73
74 /** Returns whether glanceable hub is enabled by the current user. */
75 fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean>
76
77 /**
78 * Returns true if any glanceable hub functionality should be enabled via configs and flags.
79 *
80 * This should be used for preventing basic glanceable hub functionality from running on devices
81 * that don't need it.
82 *
83 * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android
84 * config boolean. Otherwise, checks the old config_communalServiceEnabled config and
85 * communal_hub flag.
86 */
87 fun getFlagEnabled(): Boolean
88
89 /**
90 * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag
91 * are enabled.
92 *
93 * This should be used to flag off new glanceable hub or dream behavior that should launch
94 * together with the new hub experience that brings the hub to mobile.
95 *
96 * The trunk-stable flag is controlled by server rollout and is on all devices. The Android
97 * config flag is enabled via resource overlay only on products we want the hub to be present
98 * on.
99 */
100 fun getV2FlagEnabled(): Boolean
101
102 /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
103 fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
104
105 /** The type of background to use for the hub. Used to experiment with different backgrounds. */
106 fun getBackground(user: UserInfo): Flow<CommunalBackgroundType>
107 }
108
109 @SysUISingleton
110 class CommunalSettingsRepositoryImpl
111 @Inject
112 constructor(
113 @Background private val bgDispatcher: CoroutineDispatcher,
114 @Main private val resources: Resources,
115 private val featureFlagsClassic: FeatureFlagsClassic,
116 private val secureSettings: SecureSettings,
117 private val broadcastDispatcher: BroadcastDispatcher,
118 private val devicePolicyManager: DevicePolicyManager,
119 @Named(DEFAULT_BACKGROUND_TYPE) private val defaultBackgroundType: CommunalBackgroundType,
120 ) : CommunalSettingsRepository {
121
<lambda>null122 private val dreamsActivatedOnSleepByDefault by lazy {
123 resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault)
124 }
125
<lambda>null126 private val dreamsActivatedOnDockByDefault by lazy {
127 resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault)
128 }
129
<lambda>null130 private val dreamsActivatedOnPosturedByDefault by lazy {
131 resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault)
132 }
133
<lambda>null134 private val whenToStartHubByDefault by lazy {
135 resources.getInteger(com.android.internal.R.integer.config_whenToStartHubModeDefault)
136 }
137
138 private val _suppressionReasons =
139 MutableStateFlow<List<SuppressionReason>>(
140 // Suppress hub by default until we get an initial update.
141 listOf(SuppressionReason.ReasonUnknown(FEATURE_ALL))
142 )
143
isEnablednull144 override fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean> =
145 _suppressionReasons.map { reasons -> reasons.none { it.isSuppressed(feature) } }
146
setSuppressionReasonsnull147 override fun setSuppressionReasons(reasons: List<SuppressionReason>) {
148 _suppressionReasons.value = reasons
149 }
150
getFlagEnablednull151 override fun getFlagEnabled(): Boolean {
152 return if (getV2FlagEnabled()) {
153 true
154 } else {
155 // This config (exposed as a classic feature flag) is targeted only to tablet.
156 // TODO(b/379181581): clean up usages of communal_hub flag
157 featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
158 }
159 }
160
getV2FlagEnablednull161 override fun getV2FlagEnabled(): Boolean {
162 return resources.getBoolean(com.android.internal.R.bool.config_glanceableHubEnabled) &&
163 glanceableHubV2()
164 }
165
getWhenToDreamStatenull166 override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> =
167 secureSettings
168 .observerFlow(
169 userId = user.id,
170 names =
171 arrayOf(
172 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
173 Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
174 Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
175 ),
176 )
177 .emitOnStart()
178 .map {
179 if (
180 secureSettings.getBoolForUser(
181 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
182 dreamsActivatedOnSleepByDefault,
183 user.id,
184 )
185 ) {
186 WhenToDream.WHILE_CHARGING
187 } else if (
188 secureSettings.getBoolForUser(
189 Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
190 dreamsActivatedOnDockByDefault,
191 user.id,
192 )
193 ) {
194 WhenToDream.WHILE_DOCKED
195 } else if (
196 secureSettings.getBoolForUser(
197 Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
198 dreamsActivatedOnPosturedByDefault,
199 user.id,
200 )
201 ) {
202 WhenToDream.WHILE_POSTURED
203 } else {
204 WhenToDream.NEVER
205 }
206 }
207 .flowOn(bgDispatcher)
208
getWhenToStartHubStatenull209 override fun getWhenToStartHubState(user: UserInfo): Flow<WhenToStartHub> {
210 if (!getV2FlagEnabled()) {
211 return MutableStateFlow(WhenToStartHub.NEVER)
212 }
213 return secureSettings
214 .observerFlow(
215 userId = user.id,
216 names = arrayOf(Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB),
217 )
218 .emitOnStart()
219 .map {
220 when (
221 secureSettings.getIntForUser(
222 Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB,
223 whenToStartHubByDefault,
224 user.id,
225 )
226 ) {
227 Settings.Secure.GLANCEABLE_HUB_START_NEVER -> WhenToStartHub.NEVER
228 Settings.Secure.GLANCEABLE_HUB_START_CHARGING -> WhenToStartHub.WHILE_CHARGING
229 Settings.Secure.GLANCEABLE_HUB_START_CHARGING_UPRIGHT ->
230 WhenToStartHub.WHILE_CHARGING_AND_POSTURED
231
232 Settings.Secure.GLANCEABLE_HUB_START_DOCKED -> WhenToStartHub.WHILE_DOCKED
233 else -> WhenToStartHub.NEVER
234 }
235 }
236 .flowOn(bgDispatcher)
237 }
238
getAllowedByDevicePolicynull239 override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
240 broadcastDispatcher
241 .broadcastFlow(
242 filter =
243 IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
244 // In COPE management mode, the restriction from the managed profile may
245 // propagate to the main profile. Therefore listen to this broadcast across
246 // all users and update the state each time it changes.
247 user = UserHandle.ALL,
248 )
249 .emitOnStart()
250 .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
251
getBackgroundnull252 override fun getBackground(user: UserInfo): Flow<CommunalBackgroundType> =
253 secureSettings
254 .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_BACKGROUND_SETTING))
255 .emitOnStart()
256 .map {
257 val intType =
258 secureSettings.getIntForUser(
259 GLANCEABLE_HUB_BACKGROUND_SETTING,
260 defaultBackgroundType.value,
261 user.id,
262 )
263 CommunalBackgroundType.entries.find { type -> type.value == intType }
264 ?: defaultBackgroundType
265 }
266
getSettingEnabledByUsernull267 override fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean> =
268 secureSettings
269 .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
270 // Force an update
271 .emitOnStart()
272 .map {
273 secureSettings.getIntForUser(
274 Settings.Secure.GLANCEABLE_HUB_ENABLED,
275 ENABLED_SETTING_DEFAULT,
276 user.id,
277 ) == 1
278 }
279 .flowOn(bgDispatcher)
280
281 companion object {
282 const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
283 private const val ENABLED_SETTING_DEFAULT = 1
284 }
285 }
286
DevicePolicyManagernull287 private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean =
288 (getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0
289