Add an AutoAddRepository
Flag: QS_PIPELINE_AUTO_ADD Test: atest AutoAddSettingsRepositoryTest Fixes: 277945691 Change-Id: I39e092356a429d351222a14df391d10b622ce633
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs.pipeline.dagger
|
||||
|
||||
import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
|
||||
import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
abstract class QSAutoAddModule {
|
||||
|
||||
@Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import dagger.Provides
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@Module(includes = [QSAutoAddModule::class])
|
||||
abstract class QSPipelineModule {
|
||||
|
||||
/** Implementation for [TileSpecRepository] */
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs.pipeline.data.repository
|
||||
|
||||
import android.database.ContentObserver
|
||||
import android.provider.Settings
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.qs.pipeline.shared.TileSpec
|
||||
import com.android.systemui.util.settings.SecureSettings
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** Repository to track what QS tiles have been auto-added */
|
||||
interface AutoAddRepository {
|
||||
|
||||
/** Flow of tiles that have been auto-added */
|
||||
fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>>
|
||||
|
||||
/** Mark a tile as having been auto-added */
|
||||
suspend fun markTileAdded(userId: Int, spec: TileSpec)
|
||||
|
||||
/**
|
||||
* Unmark a tile as having been auto-added. This is used for tiles that can be auto-added
|
||||
* multiple times.
|
||||
*/
|
||||
suspend fun unmarkTileAdded(userId: Int, spec: TileSpec)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation that tracks the auto-added tiles stored in [Settings.Secure.QS_AUTO_ADDED_TILES].
|
||||
*/
|
||||
@SysUISingleton
|
||||
class AutoAddSettingRepository
|
||||
@Inject
|
||||
constructor(
|
||||
private val secureSettings: SecureSettings,
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
) : AutoAddRepository {
|
||||
override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
|
||||
return conflatedCallbackFlow {
|
||||
val observer =
|
||||
object : ContentObserver(null) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
secureSettings.registerContentObserverForUser(SETTING, observer, userId)
|
||||
|
||||
awaitClose { secureSettings.unregisterContentObserver(observer) }
|
||||
}
|
||||
.onStart { emit(Unit) }
|
||||
.map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
|
||||
.distinctUntilChanged()
|
||||
.map {
|
||||
it.split(DELIMITER).map(TileSpec::create).filter { it !is TileSpec.Invalid }.toSet()
|
||||
}
|
||||
.flowOn(bgDispatcher)
|
||||
}
|
||||
|
||||
override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
|
||||
if (spec is TileSpec.Invalid) {
|
||||
return
|
||||
}
|
||||
val added = load(userId).toMutableSet()
|
||||
if (added.add(spec)) {
|
||||
store(userId, added)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
|
||||
if (spec is TileSpec.Invalid) {
|
||||
return
|
||||
}
|
||||
val added = load(userId).toMutableSet()
|
||||
if (added.remove(spec)) {
|
||||
store(userId, added)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun store(userId: Int, tiles: Set<TileSpec>) {
|
||||
val toStore =
|
||||
tiles
|
||||
.filter { it !is TileSpec.Invalid }
|
||||
.joinToString(DELIMITER, transform = TileSpec::spec)
|
||||
withContext(bgDispatcher) {
|
||||
secureSettings.putStringForUser(
|
||||
SETTING,
|
||||
toStore,
|
||||
null,
|
||||
false,
|
||||
userId,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun load(userId: Int): Set<TileSpec> {
|
||||
return withContext(bgDispatcher) {
|
||||
(secureSettings.getStringForUser(SETTING, userId) ?: "")
|
||||
.split(",")
|
||||
.map(TileSpec::create)
|
||||
.filter { it !is TileSpec.Invalid }
|
||||
.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
|
||||
private const val DELIMITER = ","
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package com.android.systemui.qs.pipeline.prototyping
|
||||
|
||||
import android.util.Log
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
|
||||
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
|
||||
import com.android.systemui.qs.pipeline.shared.TileSpec
|
||||
import com.android.systemui.statusbar.commandline.Command
|
||||
@@ -46,6 +48,7 @@ class PrototypeCoreStartable
|
||||
@Inject
|
||||
constructor(
|
||||
private val tileSpecRepository: TileSpecRepository,
|
||||
private val autoAddRepository: AutoAddRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val featureFlags: FeatureFlags,
|
||||
@Application private val scope: CoroutineScope,
|
||||
@@ -60,6 +63,13 @@ constructor(
|
||||
.flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
|
||||
.collect {}
|
||||
}
|
||||
if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
|
||||
scope.launch {
|
||||
userRepository.selectedUserInfo
|
||||
.flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) }
|
||||
.collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") }
|
||||
}
|
||||
}
|
||||
commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
|
||||
}
|
||||
}
|
||||
@@ -105,5 +115,6 @@ constructor(
|
||||
|
||||
companion object {
|
||||
private const val COMMAND = "qs-pipeline"
|
||||
private const val TAG = "PrototypeCoreStartable"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs.pipeline.data.repository
|
||||
|
||||
import android.provider.Settings
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.qs.pipeline.shared.TileSpec
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class AutoAddSettingsRepositoryTest : SysuiTestCase() {
|
||||
private val secureSettings = FakeSettings()
|
||||
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
|
||||
private lateinit var underTest: AutoAddSettingRepository
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
underTest =
|
||||
AutoAddSettingRepository(
|
||||
secureSettings,
|
||||
testDispatcher,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonExistentSetting_emptySet() =
|
||||
testScope.runTest {
|
||||
val specs by collectLastValue(underTest.autoAddedTiles(0))
|
||||
|
||||
assertThat(specs).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsChange_correctValues() =
|
||||
testScope.runTest {
|
||||
val userId = 0
|
||||
val specs by collectLastValue(underTest.autoAddedTiles(userId))
|
||||
|
||||
val value = "a,custom(b/c)"
|
||||
storeForUser(value, userId)
|
||||
|
||||
assertThat(specs).isEqualTo(value.toSet())
|
||||
|
||||
val newValue = "a"
|
||||
storeForUser(newValue, userId)
|
||||
|
||||
assertThat(specs).isEqualTo(newValue.toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tilesForCorrectUsers() =
|
||||
testScope.runTest {
|
||||
val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
|
||||
val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
|
||||
|
||||
val user0Tiles = "a"
|
||||
val user1Tiles = "custom(b/c)"
|
||||
storeForUser(user0Tiles, 0)
|
||||
storeForUser(user1Tiles, 1)
|
||||
|
||||
assertThat(tilesFromUser0).isEqualTo(user0Tiles.toSet())
|
||||
assertThat(tilesFromUser1).isEqualTo(user1Tiles.toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noInvalidTileSpecs() =
|
||||
testScope.runTest {
|
||||
val userId = 0
|
||||
val tiles by collectLastValue(underTest.autoAddedTiles(userId))
|
||||
|
||||
val specs = "d,custom(bad)"
|
||||
storeForUser(specs, userId)
|
||||
|
||||
assertThat(tiles).isEqualTo("d".toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAdded() =
|
||||
testScope.runTest {
|
||||
val userId = 0
|
||||
val specs = mutableSetOf(TileSpec.create("a"))
|
||||
underTest.markTileAdded(userId, TileSpec.create("a"))
|
||||
|
||||
assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs)
|
||||
|
||||
specs.add(TileSpec.create("b"))
|
||||
underTest.markTileAdded(userId, TileSpec.create("b"))
|
||||
|
||||
assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAdded_multipleUsers() =
|
||||
testScope.runTest {
|
||||
underTest.markTileAdded(userId = 1, TileSpec.create("a"))
|
||||
|
||||
assertThat(loadForUser(0).toSet()).isEmpty()
|
||||
assertThat(loadForUser(1).toSet())
|
||||
.containsExactlyElementsIn(setOf(TileSpec.create("a")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAdded_Invalid_noop() =
|
||||
testScope.runTest {
|
||||
val userId = 0
|
||||
underTest.markTileAdded(userId, TileSpec.Invalid)
|
||||
|
||||
assertThat(loadForUser(userId).toSet()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unmarkAdded() =
|
||||
testScope.runTest {
|
||||
val userId = 0
|
||||
val specs = "a,custom(b/c)"
|
||||
storeForUser(specs, userId)
|
||||
|
||||
underTest.unmarkTileAdded(userId, TileSpec.create("a"))
|
||||
|
||||
assertThat(loadForUser(userId).toSet())
|
||||
.containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unmarkAdded_multipleUsers() =
|
||||
testScope.runTest {
|
||||
val specs = "a,b"
|
||||
storeForUser(specs, 0)
|
||||
storeForUser(specs, 1)
|
||||
|
||||
underTest.unmarkTileAdded(1, TileSpec.create("a"))
|
||||
|
||||
assertThat(loadForUser(0).toSet()).isEqualTo(specs.toSet())
|
||||
assertThat(loadForUser(1).toSet()).isEqualTo(setOf(TileSpec.create("b")))
|
||||
}
|
||||
|
||||
private fun storeForUser(specs: String, userId: Int) {
|
||||
secureSettings.putStringForUser(SETTING, specs, userId)
|
||||
}
|
||||
|
||||
private fun loadForUser(userId: Int): String {
|
||||
return secureSettings.getStringForUser(SETTING, userId) ?: ""
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
|
||||
private const val DELIMITER = ","
|
||||
|
||||
fun Set<TileSpec>.toSeparatedString() = joinToString(DELIMITER, transform = TileSpec::spec)
|
||||
|
||||
fun String.toSet(): Set<TileSpec> {
|
||||
return if (isNullOrBlank()) {
|
||||
emptySet()
|
||||
} else {
|
||||
split(DELIMITER).map(TileSpec::create).toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user