diff --git a/src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt b/src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt new file mode 100644 index 00000000000..4f283b8c22c --- /dev/null +++ b/src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt @@ -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(), KeyedObserver, KeyValueStore { + + override fun contains(key: String) = + key == SupervisionBlockExplicitSitesPreference.KEY || + key == SupervisionAllowAllSitesPreference.KEY + + override fun getValue(key: String, valueType: Class): 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 setValue(key: String, valueType: Class, 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) + } +} diff --git a/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt b/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt index aaa5a333a86..d78afb299ac 100644 --- a/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt +++ b/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt @@ -18,9 +18,7 @@ package com.android.settings.supervision import android.content.Context import androidx.preference.Preference import com.android.settings.R -import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.Permissions -import com.android.settingslib.datastore.SettingsSecureStore import com.android.settingslib.metadata.BooleanValuePreference import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.ReadWritePermit @@ -30,9 +28,10 @@ import com.android.settingslib.preference.forEachRecursively import com.android.settingslib.widget.SelectorWithWidgetPreference /** Base class of web content filters Safe sites preferences. */ -sealed class SupervisionSafeSitesPreference : - BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { - override fun storage(context: Context): KeyValueStore = SettingsSecureStore.get(context) +sealed class SupervisionSafeSitesPreference( + protected val dataStore: SupervisionSafeSitesDataStore +) : BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { + override fun storage(context: Context) = dataStore override fun getReadPermissions(context: Context) = Permissions.EMPTY @@ -64,15 +63,15 @@ sealed class SupervisionSafeSitesPreference : override fun bind(preference: Preference, metadata: PreferenceMetadata) { super.bind(preference, metadata) (preference as SelectorWithWidgetPreference).also { - // TODO(b/401568468): Set the isChecked value using stored values. - it.isChecked = (it.key == SupervisionAllowAllSitesPreference.KEY) + it.isChecked = (dataStore.getBoolean(it.key) == true) it.setOnClickListener(this) } } } /** The "Try to block explicit sites" preference. */ -class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference() { +class SupervisionBlockExplicitSitesPreference(dataStore: SupervisionSafeSitesDataStore) : + SupervisionSafeSitesPreference(dataStore) { override val key get() = KEY @@ -89,7 +88,8 @@ class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference() } /** The "Allow all sites" preference. */ -class SupervisionAllowAllSitesPreference : SupervisionSafeSitesPreference() { +class SupervisionAllowAllSitesPreference(dataStore: SupervisionSafeSitesDataStore) : + SupervisionSafeSitesPreference(dataStore) { override val key get() = KEY diff --git a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt index 0a2891b5c5e..c76b6cdd5b4 100644 --- a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt +++ b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt @@ -47,8 +47,9 @@ class SupervisionWebContentFiltersScreen : PreferenceScreenCreator { R.string.supervision_web_content_filters_browser_title, ) += { - +SupervisionBlockExplicitSitesPreference() - +SupervisionAllowAllSitesPreference() + val dataStore = SupervisionSafeSitesDataStore(context) + +SupervisionBlockExplicitSitesPreference(dataStore) + +SupervisionAllowAllSitesPreference(dataStore) } // TODO(b/401569571) implement the SafeSearch group. } diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt index 5be7a1167e4..a3aca69cf32 100644 --- a/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt +++ b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt @@ -16,20 +16,33 @@ package com.android.settings.supervision 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.ext.junit.runners.AndroidJUnit4 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 org.junit.Assert.assertThrows +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SupervisionSafeSitesPreferenceTest { 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() - - private val blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference() + @Before + fun setUp() { + dataStore = SupervisionSafeSitesDataStore(context) + allowAllSitesPreference = SupervisionAllowAllSitesPreference(dataStore) + blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference(dataStore) + } @Test fun getTitle_allowAllSites() { @@ -50,4 +63,64 @@ class SupervisionSafeSitesPreferenceTest { 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) + } } diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt index 351cbdeaa19..31bdbd29405 100644 --- a/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt +++ b/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt @@ -15,18 +15,32 @@ */ package com.android.settings.supervision +import android.app.supervision.flags.Flags 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.ext.junit.runners.AndroidJUnit4 import com.android.settings.R +import com.android.settingslib.widget.SelectorWithWidgetPreference import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SupervisionWebContentFiltersScreenTest { + @get:Rule val setFlagsRule = SetFlagsRule() private val context: Context = ApplicationProvider.getApplicationContext() - private val supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen() + private lateinit var supervisionWebContentFiltersScreen: SupervisionWebContentFiltersScreen + + @Before + fun setUp() { + supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen() + } @Test fun key() { @@ -39,4 +53,40 @@ class SupervisionWebContentFiltersScreenTest { assertThat(supervisionWebContentFiltersScreen.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( + SupervisionAllowAllSitesPreference.KEY + )!! + val blockExplicitSitesPreference = + fragment.findPreference( + SupervisionBlockExplicitSitesPreference.KEY + )!! + + assertThat(allowAllSitesPreference.isChecked).isTrue() + assertThat(blockExplicitSitesPreference.isChecked).isFalse() + + blockExplicitSitesPreference.performClick() + + assertThat(blockExplicitSitesPreference.isChecked).isTrue() + assertThat(allowAllSitesPreference.isChecked).isFalse() + } + } }