Add a persistence mechanism for panels' packages

This will allow to persist the packages of apps that provide device
controls panels after they are authorized (opted in) by the user.

Packages that are in the seeding list (config_controlsPreferredPackages)
will be auto-added, but only when creating the set of authorized package
for the first time (per user).

Test: atest AuthorizedPanelsRepositoryImplTest
Fixes: 264414289

Change-Id: Ic23e6172158bba3cf960e81919978ec62da79150
This commit is contained in:
Fabian Kozynski
2023-01-11 14:19:28 -05:00
parent 1db653833b
commit e192a439af
5 changed files with 259 additions and 1 deletions

View File

@@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingControllerImpl
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -104,6 +106,11 @@ abstract class ControlsModule {
coordinator: ControlActionCoordinatorImpl
): ControlActionCoordinator
@Binds
abstract fun provideAuthorizedPanelsRepository(
repository: AuthorizedPanelsRepositoryImpl
): AuthorizedPanelsRepository
@BindsOptionalOf
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper

View File

@@ -0,0 +1,24 @@
/*
* 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.controls.panels
interface AuthorizedPanelsRepository {
fun getAuthorizedPanels(): Set<String>
fun addAuthorizedPanels(packageNames: Set<String>)
}

View File

@@ -0,0 +1,82 @@
/*
* 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.controls.panels
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import javax.inject.Inject
class AuthorizedPanelsRepositoryImpl
@Inject
constructor(
private val context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker
) : AuthorizedPanelsRepository {
override fun getAuthorizedPanels(): Set<String> {
return getAuthorizedPanelsInternal(instantiateSharedPrefs())
}
override fun addAuthorizedPanels(packageNames: Set<String>) {
addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
}
private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> {
return sharedPreferences.getStringSet(KEY, emptySet())!!
}
private fun addAuthorizedPanelsInternal(
sharedPreferences: SharedPreferences,
packageNames: Set<String>
) {
val currentSet = getAuthorizedPanelsInternal(sharedPreferences)
sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
}
private fun instantiateSharedPrefs(): SharedPreferences {
val sharedPref =
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
Context.MODE_PRIVATE,
userTracker.userId,
)
// If we've never run this (i.e., the key doesn't exist), add the default packages
if (sharedPref.getStringSet(KEY, null) == null) {
sharedPref
.edit()
.putStringSet(
KEY,
context.resources
.getStringArray(R.array.config_controlsPreferredPackages)
.toSet()
)
.apply()
}
return sharedPref
}
companion object {
private const val KEY = "authorized_panels"
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.controls.panels
import android.content.SharedPreferences
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.File
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var userTracker: UserTracker
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf<String>()
)
whenever(userTracker.userId).thenReturn(0)
}
@Test
fun testPreApprovedPackagesAreSeededIfNoSavedPreferences() {
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE)
)
val sharedPrefs = FakeSharedPreferences()
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
val repository = createRepository(fileManager)
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
}
@Test
fun testPreApprovedPackagesNotSeededIfEmptySavedPreferences() {
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE)
)
val sharedPrefs = FakeSharedPreferences()
sharedPrefs.edit().putStringSet(KEY, emptySet()).apply()
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
createRepository(fileManager)
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
@Test
fun testPreApprovedPackagesOnlySetForUserThatDoesntHaveThem() {
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE)
)
val sharedPrefs_0 = FakeSharedPreferences()
val sharedPrefs_1 = FakeSharedPreferences()
sharedPrefs_1.edit().putStringSet(KEY, emptySet()).apply()
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs_0, 1 to sharedPrefs_1))
val repository = createRepository(fileManager)
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
whenever(userTracker.userId).thenReturn(1)
assertThat(repository.getAuthorizedPanels()).isEmpty()
}
@Test
fun testGetAuthorizedPackages() {
val sharedPrefs = FakeSharedPreferences()
sharedPrefs.edit().putStringSet(KEY, mutableSetOf(TEST_PACKAGE)).apply()
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
val repository = createRepository(fileManager)
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
}
@Test
fun testSetAuthorizedPackage() {
val sharedPrefs = FakeSharedPreferences()
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
val repository = createRepository(fileManager)
repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
}
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
}
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
UserFileManager {
override fun getFile(fileName: String, userId: Int): File {
throw UnsupportedOperationException()
}
override fun getSharedPreferences(
fileName: String,
mode: Int,
userId: Int
): SharedPreferences {
if (fileName != FILE_NAME) {
throw IllegalArgumentException("Preference files must be $FILE_NAME")
}
return sharedPrefs.getValue(userId)
}
}
companion object {
private const val FILE_NAME = "controls_prefs"
private const val KEY = "authorized_panels"
private const val TEST_PACKAGE = "package"
}
}

View File

@@ -41,7 +41,7 @@ class FakeSharedPreferences : SharedPreferences {
}
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
return data.getOrDefault(key, defValues) as? MutableSet<String>?
return (data.getOrDefault(key, defValues) as? Set<String>?)?.toMutableSet()
}
override fun getInt(key: String, defValue: Int): Int {