Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9f83d959 authored by Jonathan Scott's avatar Jonathan Scott
Browse files

add capability of providing settings metadata asynchronously

Flag: NONE Code isn't used yet - usage will be flagged
Bug: 433311576
Test: atest com.android.settingslib.metadata.PreferenceStateProvidersTest
Change-Id: I416ceb58da33a42452ed099da8eda7a99a216d1d
parent 12b00cbf
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -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:
+92 −6
Original line number Diff line number Diff line
@@ -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?
@@ -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?
@@ -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?
@@ -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.
@@ -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
+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()
    }
}