Merge "Initial skeleton of new "Supervision" settings screen with top-level entry point." into main
This commit is contained in:
25
res/drawable/ic_account_child_invert.xml
Normal file
25
res/drawable/ic_account_child_invert.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,80Q530,80 565,115Q600,150 600,200Q600,250 565,285Q530,320 480,320Q430,320 395,285Q360,250 360,200Q360,150 395,115Q430,80 480,80ZM480,360Q527,360 573,371Q619,382 656,402Q694,421 717,447Q740,473 740,504L740,736Q740,753 732,769.5Q724,786 710,800Q696,814 677.5,826Q659,838 636,848L636,758Q636,720 583.5,696Q531,672 480,672Q430,672 383.5,692.5Q337,713 326,746Q364,761 404,767Q444,773 486,774Q494,774 502.5,774Q511,774 520,774L520,878Q513,880 505.5,880Q498,880 490,880Q454,880 407.5,872Q361,864 319,847Q277,830 248.5,802.5Q220,775 220,736L220,504Q220,473 243,447Q266,421 303,402Q341,382 387,371Q433,360 480,360ZM480,600Q513,600 536.5,576.5Q560,553 560,520Q560,487 536.5,463.5Q513,440 480,440Q447,440 423.5,463.5Q400,487 400,520Q400,553 423.5,576.5Q447,600 480,600Z"/>
|
||||
</vector>
|
@@ -37,5 +37,6 @@
|
||||
<string name="menu_key_system" translatable="false">top_level_system</string>
|
||||
<string name="menu_key_about_device" translatable="false">top_level_about_device</string>
|
||||
<string name="menu_key_support" translatable="false">top_level_support</string>
|
||||
<string name="menu_key_supervision" translatable="false">top_level_supervision</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -14075,4 +14075,17 @@
|
||||
|
||||
<!-- Content description for device details icon on Connected device page [CHAR LIMIT=NONE] -->
|
||||
<string name="device_detail_icon_content_description"><xliff:g id="device_name">%1$s</xliff:g>. Configure device detail.</string>
|
||||
|
||||
<!-- Supervision settings -->
|
||||
|
||||
<!-- Title for Supervision settings menu entry [CHAR LIMIT=60] -->
|
||||
<string name="supervision_settings_title">Supervision</string>
|
||||
<!-- Description for Supervision settings menu entry [CHAR LIMIT=NONE] -->
|
||||
<string name="supervision_settings_summary">Content restrictions & other limits</string>
|
||||
<!-- Title of toggle to enable or disable device supervision [CHAR LIMIT=60] -->
|
||||
<string name="device_supervision_switch_title">Use device supervision</string>
|
||||
<!-- Description of device supervision toggle when PIN has never been set up [CHAR LIMIT=NONE] -->
|
||||
<string name="device_supervision_switch_no_pin_summary">Set up PIN to get started</string>
|
||||
<!-- Search keywords for supervision settings [CHAR LIMIT=NONE] -->
|
||||
<string name="keywords_supervision_settings">supervision, parental supervision, parental controls</string>
|
||||
</resources>
|
||||
|
@@ -220,6 +220,16 @@
|
||||
settings:highlightableMenuKey="@string/menu_key_accounts"
|
||||
settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/>
|
||||
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:key="top_level_supervision"
|
||||
android:title="@string/supervision_settings_title"
|
||||
android:summary="@string/supervision_settings_summary"
|
||||
android:icon="@drawable/ic_account_child_invert"
|
||||
android:order="5"
|
||||
android:fragment="com.android.settings.supervision.SupervisionDashboardFragment"
|
||||
settings:highlightableMenuKey="@string/menu_key_supervision"
|
||||
settings:controller="com.android.settings.supervision.TopLevelSupervisionPreferenceController"/>
|
||||
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:key="top_level_emergency"
|
||||
android:title="@string/emergency_settings_preference_title"
|
||||
|
5
src/com/android/settings/supervision/OWNERS
Normal file
5
src/com/android/settings/supervision/OWNERS
Normal file
@@ -0,0 +1,5 @@
|
||||
# Bug component: 1680938
|
||||
|
||||
jparks@google.com
|
||||
sonnyf@google.com
|
||||
yvonnejiang@google.com
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.dashboard.DashboardFragment
|
||||
|
||||
/**
|
||||
* Fragment to display the Supervision settings landing page (Settings > Supervision).
|
||||
*
|
||||
* See [SupervisionDashboardScreen] for details on the page contents.
|
||||
*
|
||||
* This class extends [DashboardFragment] in order to support dynamic settings injection.
|
||||
*/
|
||||
class SupervisionDashboardFragment : DashboardFragment() {
|
||||
|
||||
override fun getPreferenceScreenResId() = R.xml.placeholder_preference_screen
|
||||
|
||||
override fun getMetricsCategory() = SettingsEnums.SUPERVISION_DASHBOARD
|
||||
|
||||
override fun getLogTag() = TAG
|
||||
|
||||
override fun getPreferenceScreenBindingKey(context: Context) = SupervisionDashboardScreen.KEY
|
||||
|
||||
// TODO(b/383405598): redirect to Play Store if supervisor client is not
|
||||
// fully present.
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SupervisionDashboard"
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.app.supervision.flags.Flags
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.metadata.ProvidePreferenceScreen
|
||||
import com.android.settingslib.metadata.preferenceHierarchy
|
||||
import com.android.settingslib.preference.PreferenceScreenCreator
|
||||
|
||||
/**
|
||||
* Supervision settings landing page (Settings > Supervision).
|
||||
*
|
||||
* This screen typically includes three parts:
|
||||
* 1. Primary switch to toggle supervision on and off.
|
||||
* 2. List of supervision features. Individual features like website filters or bedtime schedules
|
||||
* will be listed in a group and link out to their own respective settings pages. Features
|
||||
* implemented by supervision client apps can also be dynamically injected into this group.
|
||||
* 3. Entry point to supervision PIN management settings page.
|
||||
*/
|
||||
@ProvidePreferenceScreen(SupervisionDashboardScreen.KEY)
|
||||
class SupervisionDashboardScreen : PreferenceScreenCreator {
|
||||
|
||||
override fun isFlagEnabled(context: Context) = Flags.enableSupervisionSettingsScreen()
|
||||
|
||||
override val key: String
|
||||
get() = KEY
|
||||
|
||||
override val title: Int
|
||||
get() = R.string.supervision_settings_title
|
||||
|
||||
override val summary: Int
|
||||
get() = R.string.supervision_settings_summary
|
||||
|
||||
override val icon: Int
|
||||
get() = R.drawable.ic_account_child_invert
|
||||
|
||||
override val keywords: Int
|
||||
get() = R.string.keywords_supervision_settings
|
||||
|
||||
override fun fragmentClass() = SupervisionDashboardFragment::class.java
|
||||
|
||||
override fun getPreferenceHierarchy(context: Context) =
|
||||
preferenceHierarchy(context, this) {
|
||||
+SupervisionMainSwitchPreference()
|
||||
+TitlelessPreferenceGroup("supervision_features_group_1") += {
|
||||
// Empty category for dynamic injection targeting.
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY = "top_level_supervision"
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.app.supervision.SupervisionManager
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.datastore.KeyValueStore
|
||||
import com.android.settingslib.datastore.NoOpKeyedObservable
|
||||
import com.android.settingslib.metadata.MainSwitchPreference
|
||||
import com.android.settingslib.metadata.PreferenceSummaryProvider
|
||||
import com.android.settingslib.metadata.ReadWritePermit
|
||||
import com.android.settingslib.metadata.SensitivityLevel
|
||||
|
||||
/** Main toggle to enable or disable device supervision. */
|
||||
class SupervisionMainSwitchPreference :
|
||||
MainSwitchPreference(KEY, R.string.device_supervision_switch_title), PreferenceSummaryProvider {
|
||||
|
||||
// TODO(b/383568136): Make presence of summary conditional on whether PIN
|
||||
// has been set up before or not.
|
||||
override fun getSummary(context: Context): CharSequence? =
|
||||
context.getString(R.string.device_supervision_switch_no_pin_summary)
|
||||
|
||||
override fun storage(context: Context): KeyValueStore = SupervisionMainSwitchStorage(context)
|
||||
|
||||
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
|
||||
ReadWritePermit.DISALLOW
|
||||
|
||||
override fun getWritePermit(
|
||||
context: Context,
|
||||
value: Boolean?,
|
||||
callingPid: Int,
|
||||
callingUid: Int,
|
||||
) = ReadWritePermit.DISALLOW
|
||||
|
||||
override val sensitivityLevel: Int
|
||||
get() = SensitivityLevel.HIGH_SENSITIVITY
|
||||
|
||||
// TODO(b/390505725): Listen for changes in supervision state.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private class SupervisionMainSwitchStorage(private val context: Context) :
|
||||
NoOpKeyedObservable<String>(), KeyValueStore {
|
||||
override fun contains(key: String) = key == KEY
|
||||
|
||||
override fun <T : Any> getValue(key: String, valueType: Class<T>) =
|
||||
(context.getSystemService(SupervisionManager::class.java)?.isSupervisionEnabled() ==
|
||||
true)
|
||||
as T
|
||||
|
||||
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
|
||||
// TODO(b/383402852): implement handling of main toggle.
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY = "device_supervision_switch"
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 androidx.preference.Preference
|
||||
import com.android.settingslib.metadata.PreferenceCategory
|
||||
import com.android.settingslib.metadata.PreferenceMetadata
|
||||
import com.android.settingslib.preference.PreferenceCategoryBinding
|
||||
import com.android.settingslib.widget.theme.R
|
||||
|
||||
/**
|
||||
* A [PreferenceCategory] that does not have a title, and hides the space reserved for displaying
|
||||
* the title label above the category.
|
||||
*/
|
||||
class TitlelessPreferenceGroup(override val key: String) :
|
||||
PreferenceCategory(key, title = 0), PreferenceCategoryBinding {
|
||||
|
||||
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
|
||||
preference.layoutResource = R.layout.settingslib_preference_category_no_title
|
||||
super.bind(preference, metadata)
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.app.supervision.flags.Flags
|
||||
import android.content.Context
|
||||
import com.android.settings.core.BasePreferenceController
|
||||
|
||||
/** Controller for the top level Supervision settings Preference item. */
|
||||
class TopLevelSupervisionPreferenceController(context: Context, key: String) :
|
||||
BasePreferenceController(context, key) {
|
||||
|
||||
override fun getAvailabilityStatus(): Int =
|
||||
if (Flags.enableSupervisionSettingsScreen()) AVAILABLE else UNSUPPORTED_ON_DEVICE
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.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.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SupervisionDashboardScreenTest {
|
||||
@get:Rule val setFlagsRule = SetFlagsRule()
|
||||
|
||||
private val preferenceScreenCreator = SupervisionDashboardScreen()
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@Test
|
||||
fun key() {
|
||||
assertThat(preferenceScreenCreator.key).isEqualTo(SupervisionDashboardScreen.KEY)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
|
||||
fun flagEnabled() {
|
||||
assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
|
||||
fun flagDisabled() {
|
||||
assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse()
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.app.supervision.SupervisionManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.preference.createAndBindWidget
|
||||
import com.android.settingslib.widget.MainSwitchPreference
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SupervisionMainSwitchPreferenceTest {
|
||||
private val preference = SupervisionMainSwitchPreference()
|
||||
|
||||
private val mockSupervisionManager = mock<SupervisionManager>()
|
||||
|
||||
private val appContext: Context = ApplicationProvider.getApplicationContext()
|
||||
private val context =
|
||||
object : ContextWrapper(appContext) {
|
||||
override fun getSystemService(name: String): Any =
|
||||
when (name) {
|
||||
Context.SUPERVISION_SERVICE -> mockSupervisionManager
|
||||
else -> super.getSystemService(name)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checked_supervisionEnabled_returnTrue() {
|
||||
setSupervisionEnabled(true)
|
||||
|
||||
assertThat(getMainSwitchPreference().isChecked).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checked_supervisionDisabled_returnFalse() {
|
||||
setSupervisionEnabled(false)
|
||||
|
||||
assertThat(getMainSwitchPreference().isChecked).isFalse()
|
||||
}
|
||||
|
||||
private fun getMainSwitchPreference(): MainSwitchPreference =
|
||||
preference.createAndBindWidget(context)
|
||||
|
||||
private fun setSupervisionEnabled(enabled: Boolean) =
|
||||
mockSupervisionManager.stub { on { isSupervisionEnabled } doReturn enabled }
|
||||
}
|
Reference in New Issue
Block a user