Loading packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt +4 −4 Original line number Diff line number Diff line Loading @@ -28,10 +28,10 @@ import androidx.annotation.StringRes * * Besides the existing APIs, subclass could integrate with following interface to provide more * information: * - [PreferenceTitleProvider]: provide dynamic title content * - [PreferenceSummaryProvider]: provide dynamic summary content (e.g. based on preference value) * - [PreferenceIconProvider]: provide dynamic icon content (e.g. based on flag) * - [PreferenceAvailabilityProvider]: provide preference availability (e.g. based on flag) * - [AsyncPreferenceTitleProvider]/[PreferenceTitleProvider]: provide dynamic title content * - [AsyncPreferenceSummaryProvider]/[PreferenceSummaryProvider]: provide dynamic summary content (e.g. based on preference value) * - [AsyncPreferenceIconProvider]/[PreferenceIconProvider]: provide dynamic icon content (e.g. based on flag) * - [AsyncPreferenceAvailabilityProvider]/[PreferenceAvailabilityProvider]: provide preference availability (e.g. based on flag) * - [PreferenceLifecycleProvider]: provide the lifecycle callbacks and notify state change * * Notes: Loading packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt +92 −6 Original line number Diff line number Diff line Loading @@ -27,12 +27,30 @@ import androidx.fragment.app.FragmentManager import com.android.settingslib.datastore.KeyValueStore import kotlinx.coroutines.CoroutineScope // A standard pattern in this file is an Async* interface which has a suspend function suffixed with // Async and then a synchronous subinterface with no prefixes or suffixes. Ideally the async version // would be unprefixed/suffixed and we would have Synchronous* subinterfaces - but as we are quite // deep into migrations with the current structure, and changes now would slow things now - this // structure will be used. We should later rename these classes. /** * Interface to provide dynamic preference title. * * Implement this interface implies that the preference title should not be cached for indexing. */ interface PreferenceTitleProvider { interface AsyncPreferenceTitleProvider { /** Provides preference title. */ suspend fun getTitleAsync(context: Context): CharSequence? } /** * Interface to provide dynamic preference title synchronously */ interface PreferenceTitleProvider : AsyncPreferenceTitleProvider { override suspend fun getTitleAsync(context: Context) = getTitle(context) /** Provides preference title. */ fun getTitle(context: Context): CharSequence? Loading @@ -44,7 +62,22 @@ interface PreferenceTitleProvider { * This is used to add more context to the title, and it is effective only when building indexable * data in [PreferenceSearchIndexablesProvider]. */ interface PreferenceIndexableTitleProvider { interface AsyncPreferenceIndexableTitleProvider { /** Provides preference indexable title. */ suspend fun getIndexableTitleAsync(context: Context): CharSequence? } /** * Provides preference title to be shown in search result. * * This is used to add more context to the title, and it is effective only when building indexable * data in [PreferenceSearchIndexablesProvider]. */ interface PreferenceIndexableTitleProvider : AsyncPreferenceIndexableTitleProvider { override suspend fun getIndexableTitleAsync(context: Context) = getIndexableTitle(context) /** Provides preference indexable title. */ fun getIndexableTitle(context: Context): CharSequence? Loading @@ -55,7 +88,19 @@ interface PreferenceIndexableTitleProvider { * * Implement this interface implies that the preference summary should not be cached for indexing. */ interface PreferenceSummaryProvider { interface AsyncPreferenceSummaryProvider { /** Provides preference summary. */ suspend fun getSummaryAsync(context: Context): CharSequence? } /** * Interface to provide dynamic preference summary synchronously. * * Implement this interface implies that the preference summary should not be cached for indexing. */ interface PreferenceSummaryProvider : AsyncPreferenceSummaryProvider { override suspend fun getSummaryAsync(context: Context) = getSummary(context) /** Provides preference summary. */ fun getSummary(context: Context): CharSequence? Loading @@ -66,14 +111,41 @@ interface PreferenceSummaryProvider { * * Implement this interface implies that the preference icon should not be cached for indexing. */ interface PreferenceIconProvider { interface AsyncPreferenceIconProvider { /** Provides preference icon. */ suspend fun getIconAsync(context: Context): Int } /** * Interface to provide dynamic preference icon synchronously. * * Implement this interface implies that the preference icon should not be cached for indexing. */ interface PreferenceIconProvider : AsyncPreferenceIconProvider { override suspend fun getIconAsync(context: Context) = getIcon(context) /** Provides preference icon. */ fun getIcon(context: Context): Int } /** Interface to provide the state of preference availability. */ interface PreferenceAvailabilityProvider { interface AsyncPreferenceAvailabilityProvider { /** * Provides preference availability. * * When unavailable (i.e. `false` returned), * - UI framework normally does not show the preference widget. * - If it is a preference screen, all children may be disabled (depends on UI framework * implementation). */ suspend fun isAvailableAsync(context: Context): Boolean } /** Interface to provide the state of preference availability synchronously. */ interface PreferenceAvailabilityProvider : AsyncPreferenceAvailabilityProvider { override suspend fun isAvailableAsync(context: Context) = isAvailable(context) /** * Returns if the preference is available. Loading @@ -92,7 +164,21 @@ interface PreferenceAvailabilityProvider { * See [Managed configurations](https://developer.android.com/work/managed-configurations) for the * Android Enterprise support. */ interface PreferenceRestrictionProvider { interface AsyncPreferenceRestrictionProvider { /** Returns if preference is restricted by managed configs. */ suspend fun isRestrictedAsync(context: Context): Boolean } /** * Interface to provide the managed configuration state of the preference. * * See [Managed configurations](https://developer.android.com/work/managed-configurations) for the * Android Enterprise support. */ interface PreferenceRestrictionProvider : AsyncPreferenceRestrictionProvider { override suspend fun isRestrictedAsync(context: Context) = isRestricted(context) /** Returns if preference is restricted by managed configs. */ fun isRestricted(context: Context): Boolean Loading packages/SettingsLib/Metadata/tests/src/com/android/settingslib/metadata/PreferenceStateProvidersTest.kt 0 → 100644 +96 −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 androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) class PreferenceStateProvidersTest { private val context: Context = ApplicationProvider.getApplicationContext() private val screen = mock<PreferenceScreenMetadata>() private val subScreen = mock<PreferenceScreenMetadata>() private val preference = mock<PreferenceMetadata> { on { key } doReturn "key" } @Test fun PreferenceTitleProvider_getTitleAsync_delegatesToGetTitle() = runBlocking { val provider = object : PreferenceTitleProvider { override fun getTitle(context: Context) = "title" } assertThat(provider.getTitleAsync(context)).isEqualTo("title") } @Test fun PreferenceIndexableTitleProvider_getIndexableTitleAsync_delegatesToGetIndexableTitle() = runBlocking { val provider = object : PreferenceIndexableTitleProvider { override fun getIndexableTitle(context: Context) = "indexable title" } assertThat(provider.getIndexableTitleAsync(context)).isEqualTo("indexable title") } @Test fun PreferenceSummaryProvider_getSummaryAsync_delegatesToGetSummary() = runBlocking { val provider = object : PreferenceSummaryProvider { override fun getSummary(context: Context) = "summary" } assertThat(provider.getSummaryAsync(context)).isEqualTo("summary") } @Test fun PreferenceIconProvider_getIconAsync_delegatesToGetIcon() = runBlocking { val provider = object : PreferenceIconProvider { override fun getIcon(context: Context) = 1 } assertThat(provider.getIconAsync(context)).isEqualTo(1) } @Test fun PreferenceAvailabilityProvider_isAvailableAsync_delegatesToIsAvailable() = runBlocking { val provider = object : PreferenceAvailabilityProvider { override fun isAvailable(context: Context) = true } assertThat(provider.isAvailableAsync(context)).isTrue() } @Test fun PreferenceRestrictionProvider_isRestrictedAsync_delegatesToIsRestricted() = runBlocking { val provider = object : PreferenceRestrictionProvider { override fun isRestricted(context: Context) = true } assertThat(provider.isRestrictedAsync(context)).isTrue() } } Loading
packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt +4 −4 Original line number Diff line number Diff line Loading @@ -28,10 +28,10 @@ import androidx.annotation.StringRes * * Besides the existing APIs, subclass could integrate with following interface to provide more * information: * - [PreferenceTitleProvider]: provide dynamic title content * - [PreferenceSummaryProvider]: provide dynamic summary content (e.g. based on preference value) * - [PreferenceIconProvider]: provide dynamic icon content (e.g. based on flag) * - [PreferenceAvailabilityProvider]: provide preference availability (e.g. based on flag) * - [AsyncPreferenceTitleProvider]/[PreferenceTitleProvider]: provide dynamic title content * - [AsyncPreferenceSummaryProvider]/[PreferenceSummaryProvider]: provide dynamic summary content (e.g. based on preference value) * - [AsyncPreferenceIconProvider]/[PreferenceIconProvider]: provide dynamic icon content (e.g. based on flag) * - [AsyncPreferenceAvailabilityProvider]/[PreferenceAvailabilityProvider]: provide preference availability (e.g. based on flag) * - [PreferenceLifecycleProvider]: provide the lifecycle callbacks and notify state change * * Notes: Loading
packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt +92 −6 Original line number Diff line number Diff line Loading @@ -27,12 +27,30 @@ import androidx.fragment.app.FragmentManager import com.android.settingslib.datastore.KeyValueStore import kotlinx.coroutines.CoroutineScope // A standard pattern in this file is an Async* interface which has a suspend function suffixed with // Async and then a synchronous subinterface with no prefixes or suffixes. Ideally the async version // would be unprefixed/suffixed and we would have Synchronous* subinterfaces - but as we are quite // deep into migrations with the current structure, and changes now would slow things now - this // structure will be used. We should later rename these classes. /** * Interface to provide dynamic preference title. * * Implement this interface implies that the preference title should not be cached for indexing. */ interface PreferenceTitleProvider { interface AsyncPreferenceTitleProvider { /** Provides preference title. */ suspend fun getTitleAsync(context: Context): CharSequence? } /** * Interface to provide dynamic preference title synchronously */ interface PreferenceTitleProvider : AsyncPreferenceTitleProvider { override suspend fun getTitleAsync(context: Context) = getTitle(context) /** Provides preference title. */ fun getTitle(context: Context): CharSequence? Loading @@ -44,7 +62,22 @@ interface PreferenceTitleProvider { * This is used to add more context to the title, and it is effective only when building indexable * data in [PreferenceSearchIndexablesProvider]. */ interface PreferenceIndexableTitleProvider { interface AsyncPreferenceIndexableTitleProvider { /** Provides preference indexable title. */ suspend fun getIndexableTitleAsync(context: Context): CharSequence? } /** * Provides preference title to be shown in search result. * * This is used to add more context to the title, and it is effective only when building indexable * data in [PreferenceSearchIndexablesProvider]. */ interface PreferenceIndexableTitleProvider : AsyncPreferenceIndexableTitleProvider { override suspend fun getIndexableTitleAsync(context: Context) = getIndexableTitle(context) /** Provides preference indexable title. */ fun getIndexableTitle(context: Context): CharSequence? Loading @@ -55,7 +88,19 @@ interface PreferenceIndexableTitleProvider { * * Implement this interface implies that the preference summary should not be cached for indexing. */ interface PreferenceSummaryProvider { interface AsyncPreferenceSummaryProvider { /** Provides preference summary. */ suspend fun getSummaryAsync(context: Context): CharSequence? } /** * Interface to provide dynamic preference summary synchronously. * * Implement this interface implies that the preference summary should not be cached for indexing. */ interface PreferenceSummaryProvider : AsyncPreferenceSummaryProvider { override suspend fun getSummaryAsync(context: Context) = getSummary(context) /** Provides preference summary. */ fun getSummary(context: Context): CharSequence? Loading @@ -66,14 +111,41 @@ interface PreferenceSummaryProvider { * * Implement this interface implies that the preference icon should not be cached for indexing. */ interface PreferenceIconProvider { interface AsyncPreferenceIconProvider { /** Provides preference icon. */ suspend fun getIconAsync(context: Context): Int } /** * Interface to provide dynamic preference icon synchronously. * * Implement this interface implies that the preference icon should not be cached for indexing. */ interface PreferenceIconProvider : AsyncPreferenceIconProvider { override suspend fun getIconAsync(context: Context) = getIcon(context) /** Provides preference icon. */ fun getIcon(context: Context): Int } /** Interface to provide the state of preference availability. */ interface PreferenceAvailabilityProvider { interface AsyncPreferenceAvailabilityProvider { /** * Provides preference availability. * * When unavailable (i.e. `false` returned), * - UI framework normally does not show the preference widget. * - If it is a preference screen, all children may be disabled (depends on UI framework * implementation). */ suspend fun isAvailableAsync(context: Context): Boolean } /** Interface to provide the state of preference availability synchronously. */ interface PreferenceAvailabilityProvider : AsyncPreferenceAvailabilityProvider { override suspend fun isAvailableAsync(context: Context) = isAvailable(context) /** * Returns if the preference is available. Loading @@ -92,7 +164,21 @@ interface PreferenceAvailabilityProvider { * See [Managed configurations](https://developer.android.com/work/managed-configurations) for the * Android Enterprise support. */ interface PreferenceRestrictionProvider { interface AsyncPreferenceRestrictionProvider { /** Returns if preference is restricted by managed configs. */ suspend fun isRestrictedAsync(context: Context): Boolean } /** * Interface to provide the managed configuration state of the preference. * * See [Managed configurations](https://developer.android.com/work/managed-configurations) for the * Android Enterprise support. */ interface PreferenceRestrictionProvider : AsyncPreferenceRestrictionProvider { override suspend fun isRestrictedAsync(context: Context) = isRestricted(context) /** Returns if preference is restricted by managed configs. */ fun isRestricted(context: Context): Boolean Loading
packages/SettingsLib/Metadata/tests/src/com/android/settingslib/metadata/PreferenceStateProvidersTest.kt 0 → 100644 +96 −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 androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) class PreferenceStateProvidersTest { private val context: Context = ApplicationProvider.getApplicationContext() private val screen = mock<PreferenceScreenMetadata>() private val subScreen = mock<PreferenceScreenMetadata>() private val preference = mock<PreferenceMetadata> { on { key } doReturn "key" } @Test fun PreferenceTitleProvider_getTitleAsync_delegatesToGetTitle() = runBlocking { val provider = object : PreferenceTitleProvider { override fun getTitle(context: Context) = "title" } assertThat(provider.getTitleAsync(context)).isEqualTo("title") } @Test fun PreferenceIndexableTitleProvider_getIndexableTitleAsync_delegatesToGetIndexableTitle() = runBlocking { val provider = object : PreferenceIndexableTitleProvider { override fun getIndexableTitle(context: Context) = "indexable title" } assertThat(provider.getIndexableTitleAsync(context)).isEqualTo("indexable title") } @Test fun PreferenceSummaryProvider_getSummaryAsync_delegatesToGetSummary() = runBlocking { val provider = object : PreferenceSummaryProvider { override fun getSummary(context: Context) = "summary" } assertThat(provider.getSummaryAsync(context)).isEqualTo("summary") } @Test fun PreferenceIconProvider_getIconAsync_delegatesToGetIcon() = runBlocking { val provider = object : PreferenceIconProvider { override fun getIcon(context: Context) = 1 } assertThat(provider.getIconAsync(context)).isEqualTo(1) } @Test fun PreferenceAvailabilityProvider_isAvailableAsync_delegatesToIsAvailable() = runBlocking { val provider = object : PreferenceAvailabilityProvider { override fun isAvailable(context: Context) = true } assertThat(provider.isAvailableAsync(context)).isTrue() } @Test fun PreferenceRestrictionProvider_isRestrictedAsync_delegatesToIsRestricted() = runBlocking { val provider = object : PreferenceRestrictionProvider { override fun isRestricted(context: Context) = true } assertThat(provider.isRestrictedAsync(context)).isTrue() } }