diff --git a/res/values/strings.xml b/res/values/strings.xml index ac99f2d5160..6d865c21096 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -14360,6 +14360,16 @@ Data usage charges may apply. No filter is perfect, but this should help hide sexually explicit sites Allow all sites + + Google Search + + SafeSearch filtering ON + + Helps filter out explicit images, text, and links from search results on this device + + SafeSearch filtering OFF + + Account settings may still filter or blur explicit results Enter supervision PIN diff --git a/src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt b/src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt new file mode 100644 index 00000000000..617a3451207 --- /dev/null +++ b/src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt @@ -0,0 +1,106 @@ +/* + * 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 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 +import com.android.settingslib.metadata.SensitivityLevel +import com.android.settingslib.preference.PreferenceBinding +import com.android.settingslib.preference.forEachRecursively +import com.android.settingslib.widget.SelectorWithWidgetPreference + +/** Base class of web content filters SafeSearch preferences. */ +sealed class SupervisionSafeSearchPreference : + BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { + override fun storage(context: Context): KeyValueStore = SettingsSecureStore.get(context) + + override fun getReadPermissions(context: Context) = Permissions.EMPTY + + override fun getWritePermissions(context: Context) = Permissions.EMPTY + + override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit( + context: Context, + value: Boolean?, + callingPid: Int, + callingUid: Int, + ) = ReadWritePermit.DISALLOW + + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + + override fun createWidget(context: Context) = SelectorWithWidgetPreference(context) + + override fun onRadioButtonClicked(emiter: SelectorWithWidgetPreference) { + emiter.parent?.forEachRecursively { + if (it is SelectorWithWidgetPreference) { + it.isChecked = it == emiter + } + } + } + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + (preference as SelectorWithWidgetPreference).also { + // TODO(b/401568995): Set the isChecked value using stored values. + it.isChecked = (it.key == SupervisionSearchFilterOffPreference.KEY) + it.setOnClickListener(this) + } + } +} + +/** The SafeSearch filter on preference. */ +class SupervisionSearchFilterOnPreference : SupervisionSafeSearchPreference() { + + override val key + get() = KEY + + override val title + get() = R.string.supervision_web_content_filters_search_filter_on_title + + override val summary + get() = R.string.supervision_web_content_filters_search_filter_on_summary + + companion object { + const val KEY = "web_content_filters_search_filter_on" + } +} + +/** The SafeSearch filter off preference. */ +class SupervisionSearchFilterOffPreference : SupervisionSafeSearchPreference() { + + override val key + get() = KEY + + override val title + get() = R.string.supervision_web_content_filters_search_filter_off_title + + override val summary + get() = R.string.supervision_web_content_filters_search_filter_off_summary + + companion object { + const val KEY = "web_content_filters_search_filter_off" + } +} diff --git a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt index c76b6cdd5b4..994165a8b36 100644 --- a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt +++ b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt @@ -51,11 +51,19 @@ class SupervisionWebContentFiltersScreen : PreferenceScreenCreator { +SupervisionBlockExplicitSitesPreference(dataStore) +SupervisionAllowAllSitesPreference(dataStore) } - // TODO(b/401569571) implement the SafeSearch group. + +PreferenceCategory( + SEARCH_RADIO_BUTTON_GROUP, + R.string.supervision_web_content_filters_search_title, + ) += + { + +SupervisionSearchFilterOnPreference() + +SupervisionSearchFilterOffPreference() + } } companion object { const val KEY = "supervision_web_content_filters" internal const val BROWSER_RADIO_BUTTON_GROUP = "browser_radio_button_group" + internal const val SEARCH_RADIO_BUTTON_GROUP = "search_radio_button_group" } } diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt new file mode 100644 index 00000000000..371cca3f2b5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt @@ -0,0 +1,60 @@ +/* + * 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 androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SupervisionSafeSearchPreferenceTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + private val searchFilterOnPreference = SupervisionSearchFilterOnPreference() + + private val searchFilterOffPreference = SupervisionSearchFilterOffPreference() + + @Test + fun getTitle_filterOn() { + assertThat(searchFilterOnPreference.title) + .isEqualTo(R.string.supervision_web_content_filters_search_filter_on_title) + } + + + @Test + fun getSummary_filterOn() { + assertThat(searchFilterOnPreference.summary) + .isEqualTo(R.string.supervision_web_content_filters_search_filter_on_summary) + } + + @Test + fun getTitle_filterOff() { + assertThat(searchFilterOffPreference.title) + .isEqualTo(R.string.supervision_web_content_filters_search_filter_off_title) + } + + @Test + fun getSummary_filterOff() { + assertThat(searchFilterOffPreference.summary) + .isEqualTo( + R.string.supervision_web_content_filters_search_filter_off_summary + ) + } +}