Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt +97 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher Loading @@ -28,6 +29,7 @@ class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { private val testScope = TestScope(dispatcher) @Mock private lateinit var pipelineLogger: QSPipelineLogger private val deviceProvisionedController = FakeDeviceProvisionedController() private lateinit var underTest: QSSettingsRestoredBroadcastRepository Loading @@ -38,6 +40,7 @@ class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { underTest = QSSettingsRestoredBroadcastRepository( fakeBroadcastDispatcher, deviceProvisionedController, pipelineLogger, testScope.backgroundScope, dispatcher, Loading Loading @@ -176,6 +179,100 @@ class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { } } @Test fun restoreAfterUserSetup_singleTilesRestoredBroadcast() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val tilesIntent = createRestoreIntent( RestoreType.TILES, CURRENT_TILES, RESTORED_TILES, ) sendIntentForUser(tilesIntent, user) deviceProvisionedController.setUserSetup(user) with(restoreData!!) { assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) assertThat(restoredAutoAddedTiles).isEmpty() assertThat(userId).isEqualTo(user) } } @Test fun restoreAfterUserSetup_singleAutoAddRestoredBroadcast_noRestore() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val autoAddIntent = createRestoreIntent( RestoreType.AUTOADD, CURRENT_AUTO_ADDED_TILES, RESTORED_AUTO_ADDED_TILES, ) sendIntentForUser(autoAddIntent, user) deviceProvisionedController.setUserSetup(user) assertThat(restoreData).isNull() } @Test fun restoreAfterUserSetup_otherUserFinishedSetup_noRestore() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val tilesIntent = createRestoreIntent( RestoreType.TILES, CURRENT_TILES, RESTORED_TILES, ) sendIntentForUser(tilesIntent, user) deviceProvisionedController.setUserSetup(user + 1) assertThat(restoreData).isNull() } @Test fun restoreAfterUserSetup_otherUserFinishedSetup_thenCorrectUser_restored() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val tilesIntent = createRestoreIntent( RestoreType.TILES, CURRENT_TILES, RESTORED_TILES, ) sendIntentForUser(tilesIntent, user) deviceProvisionedController.setUserSetup(user + 1) runCurrent() deviceProvisionedController.setUserSetup(user) with(restoreData!!) { assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) assertThat(restoredAutoAddedTiles).isEmpty() assertThat(userId).isEqualTo(user) } } private fun sendIntentForUser(intent: Intent, userId: Int) { fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( context, Loading packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt +80 −15 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.PackageChangeModel.Empty.user import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background Loading @@ -13,18 +15,28 @@ import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */ interface QSSettingsRestoredRepository { Loading @@ -44,34 +56,87 @@ class QSSettingsRestoredBroadcastRepository @Inject constructor( broadcastDispatcher: BroadcastDispatcher, private val deviceProvisionedController: DeviceProvisionedController, logger: QSPipelineLogger, @Application private val scope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : QSSettingsRestoredRepository { private val onUserSetupChangedForSomeUser = conflatedCallbackFlow { val callback = object : DeviceProvisionedController.DeviceProvisionedListener { override fun onUserSetupChanged() { trySend(Unit) } } deviceProvisionedController.addCallback(callback) awaitClose { deviceProvisionedController.removeCallback(callback) } } .emitOnStart() @OptIn(ExperimentalCoroutinesApi::class) override val restoreData = flow { run { val mutex = Mutex() val firstIntent = mutableMapOf<Int, Intent>() val restoresFromTwoBroadcasts: Flow<RestoreData> = broadcastDispatcher .broadcastFlow(INTENT_FILTER, UserHandle.ALL) { intent, receiver -> intent to receiver.sendingUserId } .filter { it.first.isCorrectSetting() } .collect { (intent, user) -> .mapNotNull { (intent, user) -> mutex.withLock { if (user !in firstIntent) { firstIntent[user] = intent null } else { val firstRestored = firstIntent.remove(user)!! emit(processIntents(user, firstRestored, intent)) processIntents(user, firstRestored, intent) } } } .catch { Log.e(TAG, "Error parsing broadcast", it) } val restoresFromUserSetup: Flow<RestoreData> = onUserSetupChangedForSomeUser .map { mutex.withLock { firstIntent .filter { (userId, _) -> deviceProvisionedController.isUserSetup(userId) } .onEach { firstIntent.remove(it.key) } .map { processSingleIntent(it.key, it.value) } .asFlow() } } .flattenConcat() .catch { Log.e(TAG, "Error parsing tiles intent after user setup", it) } .onEach { logger.logSettingsRestoredOnUserSetupComplete(it.userId) } merge(restoresFromTwoBroadcasts, restoresFromUserSetup) } .flowOn(backgroundDispatcher) .buffer(BUFFER_CAPACITY) .shareIn(scope, SharingStarted.Eagerly) .onEach(logger::logSettingsRestored) private fun processSingleIntent(user: Int, intent: Intent): RestoreData { intent.validateIntent() if (intent.getStringExtra(Intent.EXTRA_SETTING_NAME) != TILES_SETTING) { throw IllegalStateException( "Single intent restored for user $user is not tiles: $intent" ) } return RestoreData( (intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(), emptySet(), user, ) } private fun processIntents(user: Int, intent1: Intent, intent2: Intent): RestoreData { intent1.validateIntent() intent2.validateIntent() Loading packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +9 −0 Original line number Diff line number Diff line Loading @@ -221,6 +221,15 @@ constructor( ) } fun logSettingsRestoredOnUserSetupComplete(userId: Int) { restoreLogBuffer.log( RESTORE_TAG, LogLevel.DEBUG, { int1 = userId }, { "Restored from single intent after user setup complete for user $int1" } ) } fun logSettingsRestored(restoreData: RestoreData) { restoreLogBuffer.log( RESTORE_TAG, Loading packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt +20 −5 Original line number Diff line number Diff line Loading @@ -2,30 +2,45 @@ package com.android.systemui.statusbar.policy class FakeDeviceProvisionedController : DeviceProvisionedController { @JvmField var deviceProvisioned = true @JvmField var currentUser = 0 private val callbacks = mutableSetOf<DeviceProvisionedController.DeviceProvisionedListener>() private val usersSetup = mutableSetOf<Int>() override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { TODO("Not yet implemented") callbacks.add(listener) } override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { TODO("Not yet implemented") callbacks.remove(listener) } override fun isDeviceProvisioned() = deviceProvisioned @Deprecated("Deprecated in Java") override fun getCurrentUser(): Int { TODO("Not yet implemented") return currentUser } override fun isUserSetup(user: Int): Boolean { TODO("Not yet implemented") return user in usersSetup } override fun isCurrentUserSetup(): Boolean { TODO("Not yet implemented") return currentUser in usersSetup } override fun isFrpActive(): Boolean { TODO("Not yet implemented") } fun setCurrentUser(userId: Int) { currentUser = userId callbacks.toSet().forEach { it.onUserSwitched() } } fun setUserSetup(userId: Int) { usersSetup.add(userId) callbacks.toSet().forEach { it.onUserSetupChanged() } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt +97 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher Loading @@ -28,6 +29,7 @@ class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { private val testScope = TestScope(dispatcher) @Mock private lateinit var pipelineLogger: QSPipelineLogger private val deviceProvisionedController = FakeDeviceProvisionedController() private lateinit var underTest: QSSettingsRestoredBroadcastRepository Loading @@ -38,6 +40,7 @@ class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { underTest = QSSettingsRestoredBroadcastRepository( fakeBroadcastDispatcher, deviceProvisionedController, pipelineLogger, testScope.backgroundScope, dispatcher, Loading Loading @@ -176,6 +179,100 @@ class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { } } @Test fun restoreAfterUserSetup_singleTilesRestoredBroadcast() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val tilesIntent = createRestoreIntent( RestoreType.TILES, CURRENT_TILES, RESTORED_TILES, ) sendIntentForUser(tilesIntent, user) deviceProvisionedController.setUserSetup(user) with(restoreData!!) { assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) assertThat(restoredAutoAddedTiles).isEmpty() assertThat(userId).isEqualTo(user) } } @Test fun restoreAfterUserSetup_singleAutoAddRestoredBroadcast_noRestore() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val autoAddIntent = createRestoreIntent( RestoreType.AUTOADD, CURRENT_AUTO_ADDED_TILES, RESTORED_AUTO_ADDED_TILES, ) sendIntentForUser(autoAddIntent, user) deviceProvisionedController.setUserSetup(user) assertThat(restoreData).isNull() } @Test fun restoreAfterUserSetup_otherUserFinishedSetup_noRestore() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val tilesIntent = createRestoreIntent( RestoreType.TILES, CURRENT_TILES, RESTORED_TILES, ) sendIntentForUser(tilesIntent, user) deviceProvisionedController.setUserSetup(user + 1) assertThat(restoreData).isNull() } @Test fun restoreAfterUserSetup_otherUserFinishedSetup_thenCorrectUser_restored() = testScope.runTest { runCurrent() val restoreData by collectLastValue(underTest.restoreData) val user = 0 val tilesIntent = createRestoreIntent( RestoreType.TILES, CURRENT_TILES, RESTORED_TILES, ) sendIntentForUser(tilesIntent, user) deviceProvisionedController.setUserSetup(user + 1) runCurrent() deviceProvisionedController.setUserSetup(user) with(restoreData!!) { assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) assertThat(restoredAutoAddedTiles).isEmpty() assertThat(userId).isEqualTo(user) } } private fun sendIntentForUser(intent: Intent, userId: Int) { fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( context, Loading
packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt +80 −15 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.PackageChangeModel.Empty.user import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background Loading @@ -13,18 +15,28 @@ import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */ interface QSSettingsRestoredRepository { Loading @@ -44,34 +56,87 @@ class QSSettingsRestoredBroadcastRepository @Inject constructor( broadcastDispatcher: BroadcastDispatcher, private val deviceProvisionedController: DeviceProvisionedController, logger: QSPipelineLogger, @Application private val scope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : QSSettingsRestoredRepository { private val onUserSetupChangedForSomeUser = conflatedCallbackFlow { val callback = object : DeviceProvisionedController.DeviceProvisionedListener { override fun onUserSetupChanged() { trySend(Unit) } } deviceProvisionedController.addCallback(callback) awaitClose { deviceProvisionedController.removeCallback(callback) } } .emitOnStart() @OptIn(ExperimentalCoroutinesApi::class) override val restoreData = flow { run { val mutex = Mutex() val firstIntent = mutableMapOf<Int, Intent>() val restoresFromTwoBroadcasts: Flow<RestoreData> = broadcastDispatcher .broadcastFlow(INTENT_FILTER, UserHandle.ALL) { intent, receiver -> intent to receiver.sendingUserId } .filter { it.first.isCorrectSetting() } .collect { (intent, user) -> .mapNotNull { (intent, user) -> mutex.withLock { if (user !in firstIntent) { firstIntent[user] = intent null } else { val firstRestored = firstIntent.remove(user)!! emit(processIntents(user, firstRestored, intent)) processIntents(user, firstRestored, intent) } } } .catch { Log.e(TAG, "Error parsing broadcast", it) } val restoresFromUserSetup: Flow<RestoreData> = onUserSetupChangedForSomeUser .map { mutex.withLock { firstIntent .filter { (userId, _) -> deviceProvisionedController.isUserSetup(userId) } .onEach { firstIntent.remove(it.key) } .map { processSingleIntent(it.key, it.value) } .asFlow() } } .flattenConcat() .catch { Log.e(TAG, "Error parsing tiles intent after user setup", it) } .onEach { logger.logSettingsRestoredOnUserSetupComplete(it.userId) } merge(restoresFromTwoBroadcasts, restoresFromUserSetup) } .flowOn(backgroundDispatcher) .buffer(BUFFER_CAPACITY) .shareIn(scope, SharingStarted.Eagerly) .onEach(logger::logSettingsRestored) private fun processSingleIntent(user: Int, intent: Intent): RestoreData { intent.validateIntent() if (intent.getStringExtra(Intent.EXTRA_SETTING_NAME) != TILES_SETTING) { throw IllegalStateException( "Single intent restored for user $user is not tiles: $intent" ) } return RestoreData( (intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(), emptySet(), user, ) } private fun processIntents(user: Int, intent1: Intent, intent2: Intent): RestoreData { intent1.validateIntent() intent2.validateIntent() Loading
packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +9 −0 Original line number Diff line number Diff line Loading @@ -221,6 +221,15 @@ constructor( ) } fun logSettingsRestoredOnUserSetupComplete(userId: Int) { restoreLogBuffer.log( RESTORE_TAG, LogLevel.DEBUG, { int1 = userId }, { "Restored from single intent after user setup complete for user $int1" } ) } fun logSettingsRestored(restoreData: RestoreData) { restoreLogBuffer.log( RESTORE_TAG, Loading
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt +20 −5 Original line number Diff line number Diff line Loading @@ -2,30 +2,45 @@ package com.android.systemui.statusbar.policy class FakeDeviceProvisionedController : DeviceProvisionedController { @JvmField var deviceProvisioned = true @JvmField var currentUser = 0 private val callbacks = mutableSetOf<DeviceProvisionedController.DeviceProvisionedListener>() private val usersSetup = mutableSetOf<Int>() override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { TODO("Not yet implemented") callbacks.add(listener) } override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { TODO("Not yet implemented") callbacks.remove(listener) } override fun isDeviceProvisioned() = deviceProvisioned @Deprecated("Deprecated in Java") override fun getCurrentUser(): Int { TODO("Not yet implemented") return currentUser } override fun isUserSetup(user: Int): Boolean { TODO("Not yet implemented") return user in usersSetup } override fun isCurrentUserSetup(): Boolean { TODO("Not yet implemented") return currentUser in usersSetup } override fun isFrpActive(): Boolean { TODO("Not yet implemented") } fun setCurrentUser(userId: Int) { currentUser = userId callbacks.toSet().forEach { it.onUserSwitched() } } fun setUserSetup(userId: Int) { usersSetup.add(userId) callbacks.toSet().forEach { it.onUserSetupChanged() } } }