Loading packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt +14 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,12 @@ import kotlinx.coroutines.flow.Flow /** * Metadata of preference screen. * * [PreferenceScreenMetadata] class is reused for both screen container and entry points to maintain * the states (availability, enable, restriction, title, etc.) consistently. Different instances are * created for screen container and entry points respectively. In case the implementation would like * to perform action for container (or entry point) only, [isContainer] and [isEntryPoint] could be * leveraged to distinguish current screen metadata instance is acting as container or entry point. * * For parameterized preference screen that relies on additional information (e.g. package name, * language code) to build its content, the subclass must: * - override [arguments] in constructor Loading Loading @@ -63,6 +69,14 @@ interface PreferenceScreenMetadata : PreferenceGroup { /** Returns dynamic screen title, use [screenTitle] whenever possible. */ fun getScreenTitle(context: Context): CharSequence? = null /** Returns if current screen metadata instance is acting as container. */ fun isContainer(context: PreferenceLifecycleContext): Boolean = bindingKey == context.preferenceScreenKey /** Returns if current screen metadata instance is acting as entry point. */ fun isEntryPoint(context: PreferenceLifecycleContext): Boolean = bindingKey != context.preferenceScreenKey /** Returns the fragment class to show the preference screen. */ fun fragmentClass(): Class<out Fragment>? Loading packages/SettingsLib/Metadata/tests/Android.bp +5 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,10 @@ package { android_app { name: "SettingsLibMetadataShell", platform_apis: true, static_libs: [ "androidx.fragment_fragment-testing", "androidx.preference_preference", ], } android_robolectric_test { Loading @@ -12,6 +16,7 @@ android_robolectric_test { srcs: ["src/**/*.kt"], static_libs: [ "SettingsLibMetadata", "SettingsLibPreference-testutils", "androidx.test.ext.junit", "kotlin-parcelize-runtime", "mockito-robolectric-prebuilt", // mockito deps order matters! Loading packages/SettingsLib/Metadata/tests/src/com/android/settingslib/metadata/PreferenceScreenMetadataTest.kt 0 → 100644 +125 −0 Original line number Diff line number Diff line /* * 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.settingslib.metadata import android.content.Context import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.preference.PreferenceFragment import com.android.settingslib.preference.PreferenceScreenCreator import com.android.settingslib.preference.launchFragmentScenario import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PreferenceScreenMetadataTest { @Test fun isContainer_isEntryPoint() { val innerScreen = Screen("Screen2") val screen = object : Screen("Screen1") { override fun getPreferenceHierarchy( context: Context, coroutineScope: CoroutineScope, ) = preferenceHierarchy(context) { +innerScreen.key } } PreferenceScreenRegistry.preferenceScreenMetadataFactories = FixedArrayMap(2) { it.put(screen.key) { screen } it.put(innerScreen.key) { innerScreen } } screen .launchFragmentScenario() .onFragment { val context = screen.preferenceLifecycleContext assertThat(screen.isContainer(context)).isTrue() assertThat(screen.isEntryPoint(context)).isFalse() assertThat(innerScreen.isContainer(context)).isFalse() assertThat(innerScreen.isEntryPoint(context)).isTrue() } .close() } @Test fun isContainer_isEntryPoint_parameterizedScreen() { val innerScreen = object : Screen("Screen2", 0.toArgument()) { override val bindingKey get() = "screen2:0" } val screen = object : Screen("Screen1", 0.toArgument()) { override val bindingKey get() = "screen1:0" override fun getPreferenceHierarchy( context: Context, coroutineScope: CoroutineScope, ) = preferenceHierarchy(context) { +(innerScreen.key args 0.toArgument()) } } PreferenceScreenRegistry.preferenceScreenMetadataFactories = FixedArrayMap(2) { it.put( screen.key, object : PreferenceScreenMetadataParameterizedFactory { override fun create(context: Context, args: Bundle) = screen override fun parameters(context: Context) = flowOf(0.toArgument()) }, ) it.put( innerScreen.key, object : PreferenceScreenMetadataParameterizedFactory { override fun create(context: Context, args: Bundle) = innerScreen override fun parameters(context: Context) = flowOf(0.toArgument()) }, ) } screen .launchFragmentScenario() .onFragment { val context = screen.preferenceLifecycleContext assertThat(screen.isContainer(context)).isTrue() assertThat(screen.isEntryPoint(context)).isFalse() assertThat(innerScreen.isContainer(context)).isFalse() assertThat(innerScreen.isEntryPoint(context)).isTrue() } .close() } fun Int.toArgument() = Bundle().also { it.putInt(null, this) } open class Screen(override val key: String, override val arguments: Bundle? = null) : PreferenceScreenCreator, PreferenceLifecycleProvider { lateinit var preferenceLifecycleContext: PreferenceLifecycleContext override fun fragmentClass() = PreferenceFragment::class.java override fun onCreate(context: PreferenceLifecycleContext) { preferenceLifecycleContext = context } override fun getPreferenceHierarchy(context: Context, coroutineScope: CoroutineScope) = preferenceHierarchy(context) {} } } Loading
packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt +14 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,12 @@ import kotlinx.coroutines.flow.Flow /** * Metadata of preference screen. * * [PreferenceScreenMetadata] class is reused for both screen container and entry points to maintain * the states (availability, enable, restriction, title, etc.) consistently. Different instances are * created for screen container and entry points respectively. In case the implementation would like * to perform action for container (or entry point) only, [isContainer] and [isEntryPoint] could be * leveraged to distinguish current screen metadata instance is acting as container or entry point. * * For parameterized preference screen that relies on additional information (e.g. package name, * language code) to build its content, the subclass must: * - override [arguments] in constructor Loading Loading @@ -63,6 +69,14 @@ interface PreferenceScreenMetadata : PreferenceGroup { /** Returns dynamic screen title, use [screenTitle] whenever possible. */ fun getScreenTitle(context: Context): CharSequence? = null /** Returns if current screen metadata instance is acting as container. */ fun isContainer(context: PreferenceLifecycleContext): Boolean = bindingKey == context.preferenceScreenKey /** Returns if current screen metadata instance is acting as entry point. */ fun isEntryPoint(context: PreferenceLifecycleContext): Boolean = bindingKey != context.preferenceScreenKey /** Returns the fragment class to show the preference screen. */ fun fragmentClass(): Class<out Fragment>? Loading
packages/SettingsLib/Metadata/tests/Android.bp +5 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,10 @@ package { android_app { name: "SettingsLibMetadataShell", platform_apis: true, static_libs: [ "androidx.fragment_fragment-testing", "androidx.preference_preference", ], } android_robolectric_test { Loading @@ -12,6 +16,7 @@ android_robolectric_test { srcs: ["src/**/*.kt"], static_libs: [ "SettingsLibMetadata", "SettingsLibPreference-testutils", "androidx.test.ext.junit", "kotlin-parcelize-runtime", "mockito-robolectric-prebuilt", // mockito deps order matters! Loading
packages/SettingsLib/Metadata/tests/src/com/android/settingslib/metadata/PreferenceScreenMetadataTest.kt 0 → 100644 +125 −0 Original line number Diff line number Diff line /* * 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.settingslib.metadata import android.content.Context import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.preference.PreferenceFragment import com.android.settingslib.preference.PreferenceScreenCreator import com.android.settingslib.preference.launchFragmentScenario import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PreferenceScreenMetadataTest { @Test fun isContainer_isEntryPoint() { val innerScreen = Screen("Screen2") val screen = object : Screen("Screen1") { override fun getPreferenceHierarchy( context: Context, coroutineScope: CoroutineScope, ) = preferenceHierarchy(context) { +innerScreen.key } } PreferenceScreenRegistry.preferenceScreenMetadataFactories = FixedArrayMap(2) { it.put(screen.key) { screen } it.put(innerScreen.key) { innerScreen } } screen .launchFragmentScenario() .onFragment { val context = screen.preferenceLifecycleContext assertThat(screen.isContainer(context)).isTrue() assertThat(screen.isEntryPoint(context)).isFalse() assertThat(innerScreen.isContainer(context)).isFalse() assertThat(innerScreen.isEntryPoint(context)).isTrue() } .close() } @Test fun isContainer_isEntryPoint_parameterizedScreen() { val innerScreen = object : Screen("Screen2", 0.toArgument()) { override val bindingKey get() = "screen2:0" } val screen = object : Screen("Screen1", 0.toArgument()) { override val bindingKey get() = "screen1:0" override fun getPreferenceHierarchy( context: Context, coroutineScope: CoroutineScope, ) = preferenceHierarchy(context) { +(innerScreen.key args 0.toArgument()) } } PreferenceScreenRegistry.preferenceScreenMetadataFactories = FixedArrayMap(2) { it.put( screen.key, object : PreferenceScreenMetadataParameterizedFactory { override fun create(context: Context, args: Bundle) = screen override fun parameters(context: Context) = flowOf(0.toArgument()) }, ) it.put( innerScreen.key, object : PreferenceScreenMetadataParameterizedFactory { override fun create(context: Context, args: Bundle) = innerScreen override fun parameters(context: Context) = flowOf(0.toArgument()) }, ) } screen .launchFragmentScenario() .onFragment { val context = screen.preferenceLifecycleContext assertThat(screen.isContainer(context)).isTrue() assertThat(screen.isEntryPoint(context)).isFalse() assertThat(innerScreen.isContainer(context)).isFalse() assertThat(innerScreen.isEntryPoint(context)).isTrue() } .close() } fun Int.toArgument() = Bundle().also { it.putInt(null, this) } open class Screen(override val key: String, override val arguments: Bundle? = null) : PreferenceScreenCreator, PreferenceLifecycleProvider { lateinit var preferenceLifecycleContext: PreferenceLifecycleContext override fun fragmentClass() = PreferenceFragment::class.java override fun onCreate(context: PreferenceLifecycleContext) { preferenceLifecycleContext = context } override fun getPreferenceHierarchy(context: Context, coroutineScope: CoroutineScope) = preferenceHierarchy(context) {} } }