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

Commit 5fa4b13b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "add capability of providing settings metadata asynchronously" into main

parents fb71f0f3 9f83d959
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()
    }
}