<lambda>null1 package com.android.systemui.qs.pipeline.data.repository
2
3 import android.content.Intent
4 import android.content.IntentFilter
5 import android.os.UserHandle
6 import android.provider.Settings
7 import android.util.Log
8 import com.android.systemui.broadcast.BroadcastDispatcher
9 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
10 import com.android.systemui.common.shared.model.PackageChangeModel.Empty.user
11 import com.android.systemui.dagger.SysUISingleton
12 import com.android.systemui.dagger.qualifiers.Application
13 import com.android.systemui.dagger.qualifiers.Background
14 import com.android.systemui.qs.pipeline.data.model.RestoreData
15 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY
16 import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList
17 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
18 import com.android.systemui.statusbar.policy.DeviceProvisionedController
19 import com.android.systemui.util.kotlin.emitOnStart
20 import javax.inject.Inject
21 import kotlinx.coroutines.CoroutineDispatcher
22 import kotlinx.coroutines.CoroutineScope
23 import kotlinx.coroutines.channels.awaitClose
24 import kotlinx.coroutines.flow.Flow
25 import kotlinx.coroutines.flow.SharingStarted
26 import kotlinx.coroutines.flow.asFlow
27 import kotlinx.coroutines.flow.buffer
28 import kotlinx.coroutines.flow.catch
29 import kotlinx.coroutines.flow.filter
30 import kotlinx.coroutines.flow.flattenConcat
31 import kotlinx.coroutines.flow.flowOn
32 import kotlinx.coroutines.flow.map
33 import kotlinx.coroutines.flow.mapNotNull
34 import kotlinx.coroutines.flow.merge
35 import kotlinx.coroutines.flow.onEach
36 import kotlinx.coroutines.flow.shareIn
37 import kotlinx.coroutines.sync.Mutex
38 import kotlinx.coroutines.sync.withLock
39
40 /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */
41 interface QSSettingsRestoredRepository {
42 val restoreData: Flow<RestoreData>
43
44 companion object {
45 // This capacity is the number of restore data that we will keep buffered in the shared
46 // flow. It is unlikely that at any given time there would be this many restores being
47 // processed by consumers, but just in case that a couple of users are restored at the
48 // same time and they need to be replayed for the consumers of the flow.
49 const val BUFFER_CAPACITY = 10
50 }
51 }
52
53 @SysUISingleton
54 class QSSettingsRestoredBroadcastRepository
55 @Inject
56 constructor(
57 broadcastDispatcher: BroadcastDispatcher,
58 private val deviceProvisionedController: DeviceProvisionedController,
59 logger: QSPipelineLogger,
60 @Application private val scope: CoroutineScope,
61 @Background private val backgroundDispatcher: CoroutineDispatcher,
62 ) : QSSettingsRestoredRepository {
63
64 private val onUserSetupChangedForSomeUser =
<lambda>null65 conflatedCallbackFlow {
66 val callback =
67 object : DeviceProvisionedController.DeviceProvisionedListener {
68 override fun onUserSetupChanged() {
69 trySend(Unit)
70 }
71 }
72 deviceProvisionedController.addCallback(callback)
73 awaitClose { deviceProvisionedController.removeCallback(callback) }
74 }
75 .emitOnStart()
76
77 override val restoreData =
<lambda>null78 run {
79 val mutex = Mutex()
80 val firstIntent = mutableMapOf<Int, Intent>()
81
82 val restoresFromTwoBroadcasts: Flow<RestoreData> =
83 broadcastDispatcher
84 .broadcastFlow(INTENT_FILTER, UserHandle.ALL) { intent, receiver ->
85 intent to receiver.sendingUserId
86 }
87 .filter { it.first.isCorrectSetting() }
88 .mapNotNull { (intent, user) ->
89 mutex.withLock {
90 if (user !in firstIntent) {
91 firstIntent[user] = intent
92 null
93 } else {
94 val firstRestored = firstIntent.remove(user)!!
95 processIntents(user, firstRestored, intent)
96 }
97 }
98 }
99 .catch { Log.e(TAG, "Error parsing broadcast", it) }
100
101 val restoresFromUserSetup: Flow<RestoreData> =
102 onUserSetupChangedForSomeUser
103 .map {
104 mutex.withLock {
105 firstIntent
106 .filter { (userId, _) ->
107 deviceProvisionedController.isUserSetup(userId)
108 }
109 .onEach { firstIntent.remove(it.key) }
110 .map { processSingleIntent(it.key, it.value) }
111 .asFlow()
112 }
113 }
114 .flattenConcat()
115 .catch { Log.e(TAG, "Error parsing tiles intent after user setup", it) }
116 .onEach { logger.logSettingsRestoredOnUserSetupComplete(it.userId) }
117 merge(restoresFromTwoBroadcasts, restoresFromUserSetup)
118 }
119 .flowOn(backgroundDispatcher)
120 .buffer(BUFFER_CAPACITY)
121 .shareIn(scope, SharingStarted.Eagerly)
122 .onEach(logger::logSettingsRestored)
123
processSingleIntentnull124 private fun processSingleIntent(user: Int, intent: Intent): RestoreData {
125 intent.validateIntent()
126 if (intent.getStringExtra(Intent.EXTRA_SETTING_NAME) != TILES_SETTING) {
127 throw IllegalStateException(
128 "Single intent restored for user $user is not tiles: $intent"
129 )
130 }
131 return RestoreData(
132 (intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(),
133 emptySet(),
134 user,
135 )
136 }
137
processIntentsnull138 private fun processIntents(user: Int, intent1: Intent, intent2: Intent): RestoreData {
139 intent1.validateIntent()
140 intent2.validateIntent()
141 val setting1 = intent1.getStringExtra(Intent.EXTRA_SETTING_NAME)
142 val setting2 = intent2.getStringExtra(Intent.EXTRA_SETTING_NAME)
143 val (tiles, autoAdd) =
144 if (setting1 == TILES_SETTING && setting2 == AUTO_ADD_SETTING) {
145 intent1 to intent2
146 } else if (setting1 == AUTO_ADD_SETTING && setting2 == TILES_SETTING) {
147 intent2 to intent1
148 } else {
149 throw IllegalStateException("Wrong intents ($intent1, $intent2)")
150 }
151
152 return RestoreData(
153 (tiles.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(),
154 (autoAdd.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesSet(),
155 user,
156 )
157 }
158
159 private companion object {
160 private const val TAG = "QSSettingsRestoredBroadcastRepository"
161
162 private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
163 private const val TILES_SETTING = Settings.Secure.QS_TILES
164 private const val AUTO_ADD_SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
165 private val requiredExtras =
166 listOf(
167 Intent.EXTRA_SETTING_NAME,
168 Intent.EXTRA_SETTING_PREVIOUS_VALUE,
169 Intent.EXTRA_SETTING_NEW_VALUE,
170 )
171
isCorrectSettingnull172 private fun Intent.isCorrectSetting(): Boolean {
173 val setting = getStringExtra(Intent.EXTRA_SETTING_NAME)
174 return setting == TILES_SETTING || setting == AUTO_ADD_SETTING
175 }
176
validateIntentnull177 private fun Intent.validateIntent() {
178 requiredExtras.forEach { extra ->
179 if (!hasExtra(extra)) {
180 throw IllegalStateException("$this doesn't have $extra")
181 }
182 }
183 }
184
toTilesListnull185 private fun String.toTilesList() = toTilesList(this)
186
187 private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
188 }
189 }
190