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

Commit 5a87bd9a authored by Jacky Wang's avatar Jacky Wang
Browse files

[Catalyst] Support parameterized screens

Bug: 388420844
Flag: com.android.settings.flags.catalyst
Test: devtool
Change-Id: Iceca5d5b58187708156bbda8927e1622679d12ba
parent 76666b30
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -26,6 +26,14 @@ message PreferenceScreenProto {
  optional PreferenceGroupProto root = 2;
  // If the preference screen provides complete hierarchy by source code.
  optional bool complete_hierarchy = 3;
  // Parameterized screens (not recursive, provided on the top level only)
  repeated ParameterizedPreferenceScreenProto parameterized_screens = 4;
}

// Proto of parameterized preference screen
message ParameterizedPreferenceScreenProto {
  optional BundleProto args = 1;
  optional PreferenceScreenProto screen = 2;
}

// Proto of PreferenceGroup.
+23 −16
Original line number Diff line number Diff line
@@ -18,12 +18,14 @@ package com.android.settingslib.graph

import android.app.Application
import android.os.Bundle
import android.os.Parcelable
import android.os.SystemClock
import com.android.settingslib.graph.proto.PreferenceGraphProto
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.ipc.MessageCodec
import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger
import com.android.settingslib.metadata.PreferenceScreenCoordinate
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
@@ -59,10 +61,9 @@ class GetPreferenceGraphApiHandler(
        var success = false
        try {
            val builder = PreferenceGraphBuilder.of(application, callingPid, callingUid, request)
            if (request.screenKeys.isEmpty()) {
                PreferenceScreenRegistry.preferenceScreenMetadataFactories.forEachKeyAsync {
                    builder.addPreferenceScreenFromRegistry(it)
                }
            if (request.screens.isEmpty()) {
                val factories = PreferenceScreenRegistry.preferenceScreenMetadataFactories
                factories.forEachAsync { _, factory -> builder.addPreferenceScreen(factory) }
                for (provider in preferenceScreenProviders) {
                    builder.addPreferenceScreenProvider(provider)
                }
@@ -84,15 +85,15 @@ class GetPreferenceGraphApiHandler(
/**
 * Request of [GetPreferenceGraphApiHandler].
 *
 * @param screenKeys screen keys of the preference graph
 * @param visitedScreens keys of the visited preference screen
 * @param screens screens of the preference graph
 * @param visitedScreens visited preference screens
 * @param locale locale of the preference graph
 */
data class GetPreferenceGraphRequest
@JvmOverloads
constructor(
    val screenKeys: Set<String> = setOf(),
    val visitedScreens: Set<String> = setOf(),
    val screens: Set<PreferenceScreenCoordinate> = setOf(),
    val visitedScreens: Set<PreferenceScreenCoordinate> = setOf(),
    val locale: Locale? = null,
    val flags: Int = PreferenceGetterFlags.ALL,
    val includeValueDescriptor: Boolean = true,
@@ -101,26 +102,32 @@ constructor(
object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
    override fun encode(data: GetPreferenceGraphRequest): Bundle =
        Bundle(4).apply {
            putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray())
            putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray())
            putParcelableArray(KEY_SCREENS, data.screens.toTypedArray())
            putParcelableArray(KEY_VISITED_SCREENS, data.visitedScreens.toTypedArray())
            putString(KEY_LOCALE, data.locale?.toLanguageTag())
            putInt(KEY_FLAGS, data.flags)
        }

    @Suppress("DEPRECATION")
    override fun decode(data: Bundle): GetPreferenceGraphRequest {
        val screenKeys = data.getStringArray(KEY_SCREEN_KEYS) ?: arrayOf()
        val visitedScreens = data.getStringArray(KEY_VISITED_KEYS) ?: arrayOf()
        data.classLoader = PreferenceScreenCoordinate::class.java.classLoader
        val screens = data.getParcelableArray(KEY_SCREENS) ?: arrayOf()
        val visitedScreens = data.getParcelableArray(KEY_VISITED_SCREENS) ?: arrayOf()
        fun String?.toLocale() = if (this != null) Locale.forLanguageTag(this) else null
        fun Array<Parcelable>.toScreenCoordinates() =
            buildSet(size) {
                for (element in this@toScreenCoordinates) add(element as PreferenceScreenCoordinate)
            }
        return GetPreferenceGraphRequest(
            screenKeys.toSet(),
            visitedScreens.toSet(),
            screens.toScreenCoordinates(),
            visitedScreens.toScreenCoordinates(),
            data.getString(KEY_LOCALE).toLocale(),
            data.getInt(KEY_FLAGS),
        )
    }

    private const val KEY_SCREEN_KEYS = "k"
    private const val KEY_VISITED_KEYS = "v"
    private const val KEY_SCREENS = "s"
    private const val KEY_VISITED_SCREENS = "v"
    private const val KEY_LOCALE = "l"
    private const val KEY_FLAGS = "f"
}
+5 −2
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.metadata.PreferenceCoordinate
import com.android.settingslib.metadata.PreferenceHierarchyNode
import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger
import com.android.settingslib.metadata.PreferenceScreenCoordinate
import com.android.settingslib.metadata.PreferenceScreenRegistry

/**
@@ -105,8 +106,10 @@ class PreferenceGetterApiHandler(
        val errors = mutableMapOf<PreferenceCoordinate, Int>()
        val preferences = mutableMapOf<PreferenceCoordinate, PreferenceProto>()
        val flags = request.flags
        for ((screenKey, coordinates) in request.preferences.groupBy { it.screenKey }) {
            val screenMetadata = PreferenceScreenRegistry.create(application, screenKey)
        val groups =
            request.preferences.groupBy { PreferenceScreenCoordinate(it.screenKey, it.args) }
        for ((screen, coordinates) in groups) {
            val screenMetadata = PreferenceScreenRegistry.create(application, screen)
            if (screenMetadata == null) {
                val latencyMs = SystemClock.elapsedRealtime() - elapsedRealtime
                for (coordinate in coordinates) {
+58 −31
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
import com.android.settingslib.graph.proto.TextProto
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
import com.android.settingslib.metadata.IntRangeValuePreference
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
@@ -47,7 +48,10 @@ import com.android.settingslib.metadata.PreferenceHierarchy
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
import com.android.settingslib.metadata.PreferenceScreenCoordinate
import com.android.settingslib.metadata.PreferenceScreenMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadataFactory
import com.android.settingslib.metadata.PreferenceScreenMetadataParameterizedFactory
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
@@ -72,15 +76,19 @@ private constructor(
        PreferenceScreenFactory(context.ofLocale(request.locale))
    }
    private val builder by lazy { PreferenceGraphProto.newBuilder() }
    private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
    private val visitedScreens = request.visitedScreens.toMutableSet()
    private val screens = mutableMapOf<String, PreferenceScreenProto.Builder>()

    private suspend fun init() {
        for (key in request.screenKeys) {
            addPreferenceScreenFromRegistry(key)
        for (screen in request.screens) {
            PreferenceScreenRegistry.create(context, screen)?.let { addPreferenceScreen(it) }
        }
    }

    fun build(): PreferenceGraphProto = builder.build()
    fun build(): PreferenceGraphProto {
        for ((key, screenBuilder) in screens) builder.putScreens(key, screenBuilder.build())
        return builder.build()
    }

    /**
     * Adds an activity to the graph.
@@ -138,17 +146,10 @@ private constructor(
            null
        }

    suspend fun addPreferenceScreenFromRegistry(key: String): Boolean {
        val metadata = PreferenceScreenRegistry.create(context, key) ?: return false
        return addPreferenceScreenMetadata(metadata)
    }

    private suspend fun addPreferenceScreenMetadata(metadata: PreferenceScreenMetadata): Boolean =
        addPreferenceScreen(metadata.key) {
            preferenceScreenProto {
                completeHierarchy = metadata.hasCompleteHierarchy()
                root = metadata.getPreferenceHierarchy(context).toProto(metadata, true)
            }
    private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean {
        val factory =
            PreferenceScreenRegistry.preferenceScreenMetadataFactories[key] ?: return false
        return addPreferenceScreen(factory)
    }

    suspend fun addPreferenceScreenProvider(activityClass: Class<*>) {
@@ -188,25 +189,51 @@ private constructor(
            Log.e(TAG, "\"$preferenceScreen\" has no key")
            return
        }
        @Suppress("CheckReturnValue") addPreferenceScreen(key) { preferenceScreen.toProto(intent) }
        val args = preferenceScreen.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS)
        @Suppress("CheckReturnValue")
        addPreferenceScreen(key, args) {
            this.intent = intent.toProto()
            root = preferenceScreen.toProto()
        }
    }

    suspend fun addPreferenceScreen(factory: PreferenceScreenMetadataFactory): Boolean {
        if (factory is PreferenceScreenMetadataParameterizedFactory) {
            factory.parameters(context).collect { addPreferenceScreen(factory.create(context, it)) }
            return true
        }
        return addPreferenceScreen(factory.create(context))
    }

    private suspend fun addPreferenceScreen(metadata: PreferenceScreenMetadata): Boolean =
        addPreferenceScreen(metadata.key, metadata.arguments) {
            completeHierarchy = metadata.hasCompleteHierarchy()
            root = metadata.getPreferenceHierarchy(context).toProto(metadata, true)
        }

    private suspend fun addPreferenceScreen(
        key: String,
        preferenceScreenProvider: suspend () -> PreferenceScreenProto,
    ): Boolean =
        if (visitedScreens.add(key)) {
            builder.putScreens(key, preferenceScreenProvider())
            true
        } else {
            Log.w(TAG, "$key visited")
            false
        args: Bundle?,
        init: suspend PreferenceScreenProto.Builder.() -> Unit,
    ): Boolean {
        if (!visitedScreens.add(PreferenceScreenCoordinate(key, args))) {
            Log.w(TAG, "$key $args visited")
            return false
        }

    private suspend fun PreferenceScreen.toProto(intent: Intent?): PreferenceScreenProto =
        preferenceScreenProto {
            intent?.let { this.intent = it.toProto() }
            root = (this@toProto as PreferenceGroup).toProto()
        if (args == null) { // normal screen
            screens[key] = PreferenceScreenProto.newBuilder().also { init(it) }
        } else if (args.isEmpty) { // parameterized screen with backward compatibility
            val builder = screens.getOrPut(key) { PreferenceScreenProto.newBuilder() }
            init(builder)
        } else { // parameterized screen with non-empty arguments
            val builder = screens.getOrPut(key) { PreferenceScreenProto.newBuilder() }
            val parameterizedScreen = parameterizedPreferenceScreenProto {
                setArgs(args.toProto())
                setScreen(PreferenceScreenProto.newBuilder().also { init(it) })
            }
            builder.addParameterizedScreens(parameterizedScreen)
        }
        return true
    }

    private suspend fun PreferenceGroup.toProto(): PreferenceGroupProto = preferenceGroupProto {
@@ -271,7 +298,7 @@ private constructor(
            .toProto(context, callingPid, callingUid, screenMetadata, isRoot, request.flags)
            .also {
                if (metadata is PreferenceScreenMetadata) {
                    @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata)
                    @Suppress("CheckReturnValue") addPreferenceScreen(metadata)
                }
                metadata.intent(context)?.resolveActivity(context.packageManager)?.let {
                    if (it.packageName == context.packageName) {
@@ -322,7 +349,7 @@ private constructor(
                val screenKey = screen?.key
                if (!screenKey.isNullOrEmpty()) {
                    @Suppress("CheckReturnValue")
                    addPreferenceScreen(screenKey) { screen.toProto(null) }
                    addPreferenceScreen(screenKey, null) { root = screen.toProto() }
                    return actionTargetProto { key = screenKey }
                }
            } catch (e: Exception) {
+11 −7
Original line number Diff line number Diff line
@@ -40,11 +40,12 @@ import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIV
import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY

/** Request to set preference value. */
data class PreferenceSetterRequest(
    val screenKey: String,
    val key: String,
class PreferenceSetterRequest(
    screenKey: String,
    args: Bundle?,
    key: String,
    val value: PreferenceValueProto,
)
) : PreferenceCoordinate(screenKey, args, key)

/** Result of preference setter request. */
@IntDef(
@@ -121,7 +122,7 @@ class PreferenceSetterApiHandler(
            metricsLogger?.logSetterApi(
                application,
                callingUid,
                PreferenceCoordinate(request.screenKey, request.key),
                request,
                null,
                null,
                PreferenceSetterResult.UNSUPPORTED,
@@ -130,7 +131,7 @@ class PreferenceSetterApiHandler(
            return PreferenceSetterResult.UNSUPPORTED
        }
        val screenMetadata =
            PreferenceScreenRegistry.create(application, request.screenKey) ?: return notFound()
            PreferenceScreenRegistry.create(application, request) ?: return notFound()
        val key = request.key
        val metadata =
            screenMetadata.getPreferenceHierarchy(application).find(key) ?: return notFound()
@@ -199,7 +200,7 @@ class PreferenceSetterApiHandler(
        metricsLogger?.logSetterApi(
            application,
            callingUid,
            PreferenceCoordinate(request.screenKey, request.key),
            request,
            screenMetadata,
            metadata,
            result,
@@ -235,6 +236,7 @@ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> {
    override fun encode(data: PreferenceSetterRequest) =
        Bundle(3).apply {
            putString(SCREEN_KEY, data.screenKey)
            putBundle(ARGS, data.args)
            putString(KEY, data.key)
            putByteArray(null, data.value.toByteArray())
        }
@@ -242,10 +244,12 @@ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> {
    override fun decode(data: Bundle) =
        PreferenceSetterRequest(
            data.getString(SCREEN_KEY)!!,
            data.getBundle(ARGS),
            data.getString(KEY)!!,
            PreferenceValueProto.parseFrom(data.getByteArray(null)!!),
        )

    private const val SCREEN_KEY = "s"
    private const val KEY = "k"
    private const val ARGS = "a"
}
Loading