diff --git a/res/drawable/ic_account_child_invert.xml b/res/drawable/ic_account_child_invert.xml
new file mode 100644
index 00000000000..b6a799abb37
--- /dev/null
+++ b/res/drawable/ic_account_child_invert.xml
@@ -0,0 +1,25 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/values/menu_keys.xml b/res/values/menu_keys.xml
index 36fdb818c6c..5a589e466bf 100755
--- a/res/values/menu_keys.xml
+++ b/res/values/menu_keys.xml
@@ -37,5 +37,6 @@
top_level_system
top_level_about_device
top_level_support
+ top_level_supervision
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f92ecc8c832..3ac0a600d94 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -14075,4 +14075,17 @@
%1$s. Configure device detail.
+
+
+
+
+ Supervision
+
+ Content restrictions & other limits
+
+ Use device supervision
+
+ Set up PIN to get started
+
+ supervision, parental supervision, parental controls
diff --git a/res/xml/top_level_settings_v2.xml b/res/xml/top_level_settings_v2.xml
index 4bc66f66e2c..5caae07dc4b 100644
--- a/res/xml/top_level_settings_v2.xml
+++ b/res/xml/top_level_settings_v2.xml
@@ -220,6 +220,16 @@
settings:highlightableMenuKey="@string/menu_key_accounts"
settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/>
+
+
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"
+ }
+}
diff --git a/src/com/android/settings/supervision/SupervisionDashboardScreen.kt b/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
new file mode 100644
index 00000000000..f31b0bdfeaf
--- /dev/null
+++ b/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
@@ -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"
+ }
+}
diff --git a/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt b/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
new file mode 100644
index 00000000000..e7d1d7f25b0
--- /dev/null
+++ b/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
@@ -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(), KeyValueStore {
+ override fun contains(key: String) = key == KEY
+
+ override fun getValue(key: String, valueType: Class) =
+ (context.getSystemService(SupervisionManager::class.java)?.isSupervisionEnabled() ==
+ true)
+ as T
+
+ override fun setValue(key: String, valueType: Class, value: T?) {
+ // TODO(b/383402852): implement handling of main toggle.
+ }
+ }
+
+ companion object {
+ const val KEY = "device_supervision_switch"
+ }
+}
diff --git a/src/com/android/settings/supervision/TitlelessPreferenceGroup.kt b/src/com/android/settings/supervision/TitlelessPreferenceGroup.kt
new file mode 100644
index 00000000000..b0f3208bd2b
--- /dev/null
+++ b/src/com/android/settings/supervision/TitlelessPreferenceGroup.kt
@@ -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)
+ }
+}
diff --git a/src/com/android/settings/supervision/TopLevelSupervisionPreferenceController.kt b/src/com/android/settings/supervision/TopLevelSupervisionPreferenceController.kt
new file mode 100644
index 00000000000..c9979d9bdbf
--- /dev/null
+++ b/src/com/android/settings/supervision/TopLevelSupervisionPreferenceController.kt
@@ -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
+}
diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
new file mode 100644
index 00000000000..eef9ea4bbdb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
@@ -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()
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
new file mode 100644
index 00000000000..4a4acfea7b1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
@@ -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()
+
+ 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 }
+}