Create data store for supervision safe sites preference.

Test: atest SupervisionWebContentFiltersScreenTest
Test: atest SupervisionSafeSitesPreferenceTest
Test: deployed locally to a physical device
Flag: android.app.supervision.flags.enable_web_content_filters_screen
Bug: 401568468
Change-Id: I7fe8a9c5932b4c8f63c4067ba6914eb73d0e2373
This commit is contained in:
Xiaomiao Zhang
2025-03-12 21:49:31 +00:00
parent a9ef330701
commit 4d0be536c7
5 changed files with 214 additions and 15 deletions

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2025 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.settings.supervision
import android.content.Context
import android.provider.Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedObserver
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.datastore.SettingsStore
/** Datastore of the safe sites preference. */
@Suppress("UNCHECKED_CAST")
class SupervisionSafeSitesDataStore(
private val context: Context,
private val settingsStore: SettingsStore = SettingsSecureStore.get(context),
) : AbstractKeyedDataObservable<String>(), KeyedObserver<String>, KeyValueStore {
override fun contains(key: String) =
key == SupervisionBlockExplicitSitesPreference.KEY ||
key == SupervisionAllowAllSitesPreference.KEY
override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
val settingValue = (settingsStore.getBoolean(BROWSER_CONTENT_FILTERS_ENABLED) == true)
return when (key) {
SupervisionAllowAllSitesPreference.KEY -> !settingValue
SupervisionBlockExplicitSitesPreference.KEY -> settingValue
else -> null
}
as T?
}
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
if (value !is Boolean) return
when (key) {
SupervisionAllowAllSitesPreference.KEY ->
settingsStore.setBoolean(BROWSER_CONTENT_FILTERS_ENABLED, !value)
SupervisionBlockExplicitSitesPreference.KEY ->
settingsStore.setBoolean(BROWSER_CONTENT_FILTERS_ENABLED, value)
}
}
override fun onFirstObserverAdded() {
// observe the underlying storage key
settingsStore.addObserver(BROWSER_CONTENT_FILTERS_ENABLED, this, HandlerExecutor.main)
}
override fun onKeyChanged(key: String, reason: Int) {
// forward data change to preference hierarchy key
notifyChange(SupervisionBlockExplicitSitesPreference.KEY, reason)
notifyChange(SupervisionAllowAllSitesPreference.KEY, reason)
}
override fun onLastObserverRemoved() {
settingsStore.removeObserver(BROWSER_CONTENT_FILTERS_ENABLED, this)
}
}

View File

@@ -18,9 +18,7 @@ package com.android.settings.supervision
import android.content.Context import android.content.Context
import androidx.preference.Preference import androidx.preference.Preference
import com.android.settings.R import com.android.settings.R
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.Permissions import com.android.settingslib.datastore.Permissions
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.metadata.BooleanValuePreference import com.android.settingslib.metadata.BooleanValuePreference
import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.ReadWritePermit
@@ -30,9 +28,10 @@ import com.android.settingslib.preference.forEachRecursively
import com.android.settingslib.widget.SelectorWithWidgetPreference import com.android.settingslib.widget.SelectorWithWidgetPreference
/** Base class of web content filters Safe sites preferences. */ /** Base class of web content filters Safe sites preferences. */
sealed class SupervisionSafeSitesPreference : sealed class SupervisionSafeSitesPreference(
BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { protected val dataStore: SupervisionSafeSitesDataStore
override fun storage(context: Context): KeyValueStore = SettingsSecureStore.get(context) ) : BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding {
override fun storage(context: Context) = dataStore
override fun getReadPermissions(context: Context) = Permissions.EMPTY override fun getReadPermissions(context: Context) = Permissions.EMPTY
@@ -64,15 +63,15 @@ sealed class SupervisionSafeSitesPreference :
override fun bind(preference: Preference, metadata: PreferenceMetadata) { override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata) super.bind(preference, metadata)
(preference as SelectorWithWidgetPreference).also { (preference as SelectorWithWidgetPreference).also {
// TODO(b/401568468): Set the isChecked value using stored values. it.isChecked = (dataStore.getBoolean(it.key) == true)
it.isChecked = (it.key == SupervisionAllowAllSitesPreference.KEY)
it.setOnClickListener(this) it.setOnClickListener(this)
} }
} }
} }
/** The "Try to block explicit sites" preference. */ /** The "Try to block explicit sites" preference. */
class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference() { class SupervisionBlockExplicitSitesPreference(dataStore: SupervisionSafeSitesDataStore) :
SupervisionSafeSitesPreference(dataStore) {
override val key override val key
get() = KEY get() = KEY
@@ -89,7 +88,8 @@ class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference()
} }
/** The "Allow all sites" preference. */ /** The "Allow all sites" preference. */
class SupervisionAllowAllSitesPreference : SupervisionSafeSitesPreference() { class SupervisionAllowAllSitesPreference(dataStore: SupervisionSafeSitesDataStore) :
SupervisionSafeSitesPreference(dataStore) {
override val key override val key
get() = KEY get() = KEY

View File

@@ -47,8 +47,9 @@ class SupervisionWebContentFiltersScreen : PreferenceScreenCreator {
R.string.supervision_web_content_filters_browser_title, R.string.supervision_web_content_filters_browser_title,
) += ) +=
{ {
+SupervisionBlockExplicitSitesPreference() val dataStore = SupervisionSafeSitesDataStore(context)
+SupervisionAllowAllSitesPreference() +SupervisionBlockExplicitSitesPreference(dataStore)
+SupervisionAllowAllSitesPreference(dataStore)
} }
// TODO(b/401569571) implement the SafeSearch group. // TODO(b/401569571) implement the SafeSearch group.
} }

View File

@@ -16,20 +16,33 @@
package com.android.settings.supervision package com.android.settings.supervision
import android.content.Context import android.content.Context
import android.provider.Settings
import android.provider.Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED
import android.provider.Settings.SettingNotFoundException
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R import com.android.settings.R
import com.android.settingslib.preference.createAndBindWidget
import com.android.settingslib.widget.SelectorWithWidgetPreference
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SupervisionSafeSitesPreferenceTest { class SupervisionSafeSitesPreferenceTest {
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context = ApplicationProvider.getApplicationContext()
private lateinit var dataStore: SupervisionSafeSitesDataStore
private lateinit var allowAllSitesPreference: SupervisionAllowAllSitesPreference
private lateinit var blockExplicitSitesPreference: SupervisionBlockExplicitSitesPreference
private val allowAllSitesPreference = SupervisionAllowAllSitesPreference() @Before
fun setUp() {
private val blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference() dataStore = SupervisionSafeSitesDataStore(context)
allowAllSitesPreference = SupervisionAllowAllSitesPreference(dataStore)
blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference(dataStore)
}
@Test @Test
fun getTitle_allowAllSites() { fun getTitle_allowAllSites() {
@@ -50,4 +63,64 @@ class SupervisionSafeSitesPreferenceTest {
R.string.supervision_web_content_filters_browser_block_explicit_sites_summary R.string.supervision_web_content_filters_browser_block_explicit_sites_summary
) )
} }
@Test
fun allowAllSitesIsChecked_whenNoValueIsSet() {
assertThrows(SettingNotFoundException::class.java) {
Settings.Secure.getInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED)
}
assertThat(getBlockExplicitSitesWidget().isChecked).isFalse()
assertThat(getAllowAllSitesWidget().isChecked).isTrue()
}
@Test
fun blockExplicitSitesIsChecked_whenPreviouslyEnabled() {
Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 1)
assertThat(getAllowAllSitesWidget().isChecked).isFalse()
assertThat(getBlockExplicitSitesWidget().isChecked).isTrue()
}
@Test
fun clickBlockExplicitSites_enablesFilter() {
Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 0)
val blockExplicitSitesWidget = getBlockExplicitSitesWidget()
assertThat(blockExplicitSitesWidget.isChecked).isFalse()
blockExplicitSitesWidget.performClick()
assertThat(
Settings.Secure.getInt(
context.getContentResolver(),
BROWSER_CONTENT_FILTERS_ENABLED,
)
)
.isEqualTo(1)
assertThat(blockExplicitSitesWidget.isChecked).isTrue()
}
@Test
fun clickAllowAllSites_disablesFilter() {
Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 1)
val allowAllSitesWidget = getAllowAllSitesWidget()
assertThat(allowAllSitesWidget.isChecked).isFalse()
allowAllSitesWidget.performClick()
assertThat(
Settings.Secure.getInt(
context.getContentResolver(),
BROWSER_CONTENT_FILTERS_ENABLED,
)
)
.isEqualTo(0)
assertThat(allowAllSitesWidget.isChecked).isTrue()
}
private fun getBlockExplicitSitesWidget(): SelectorWithWidgetPreference {
return blockExplicitSitesPreference.createAndBindWidget(context)
}
private fun getAllowAllSitesWidget(): SelectorWithWidgetPreference {
return allowAllSitesPreference.createAndBindWidget(context)
}
} }

View File

@@ -15,18 +15,32 @@
*/ */
package com.android.settings.supervision package com.android.settings.supervision
import android.app.supervision.flags.Flags
import android.content.Context import android.content.Context
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.fragment.app.testing.FragmentScenario
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R import com.android.settings.R
import com.android.settingslib.widget.SelectorWithWidgetPreference
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SupervisionWebContentFiltersScreenTest { class SupervisionWebContentFiltersScreenTest {
@get:Rule val setFlagsRule = SetFlagsRule()
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context = ApplicationProvider.getApplicationContext()
private val supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen() private lateinit var supervisionWebContentFiltersScreen: SupervisionWebContentFiltersScreen
@Before
fun setUp() {
supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen()
}
@Test @Test
fun key() { fun key() {
@@ -39,4 +53,40 @@ class SupervisionWebContentFiltersScreenTest {
assertThat(supervisionWebContentFiltersScreen.title) assertThat(supervisionWebContentFiltersScreen.title)
.isEqualTo(R.string.supervision_web_content_filters_title) .isEqualTo(R.string.supervision_web_content_filters_title)
} }
@Test
@EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
fun flagEnabled() {
assertThat(supervisionWebContentFiltersScreen.isFlagEnabled(context)).isTrue()
}
@Test
@DisableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
fun flagDisabled() {
assertThat(supervisionWebContentFiltersScreen.isFlagEnabled(context)).isFalse()
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
fun switchSafeSitesPreferences() {
FragmentScenario.launchInContainer(supervisionWebContentFiltersScreen.fragmentClass())
.onFragment { fragment ->
val allowAllSitesPreference =
fragment.findPreference<SelectorWithWidgetPreference>(
SupervisionAllowAllSitesPreference.KEY
)!!
val blockExplicitSitesPreference =
fragment.findPreference<SelectorWithWidgetPreference>(
SupervisionBlockExplicitSitesPreference.KEY
)!!
assertThat(allowAllSitesPreference.isChecked).isTrue()
assertThat(blockExplicitSitesPreference.isChecked).isFalse()
blockExplicitSitesPreference.performClick()
assertThat(blockExplicitSitesPreference.isChecked).isTrue()
assertThat(allowAllSitesPreference.isChecked).isFalse()
}
}
} }