Loading packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +16 −14 Original line number Diff line number Diff line Loading @@ -21,10 +21,11 @@ import android.os.Bundle import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.PreferenceScreenRegistry import java.util.Locale /** API to get preference graph. */ abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<String>) : abstract class GetPreferenceGraphApiHandler : ApiHandler<GetPreferenceGraphRequest, PreferenceGraphProto> { override val requestCodec: MessageCodec<GetPreferenceGraphRequest> Loading @@ -40,8 +41,9 @@ abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<Str request: GetPreferenceGraphRequest, ): PreferenceGraphProto { val builderRequest = if (request.activityClasses.isEmpty()) { GetPreferenceGraphRequest(activityClasses, request.visitedScreens, request.locale) if (request.screenKeys.isEmpty()) { val keys = PreferenceScreenRegistry.preferenceScreens.keys GetPreferenceGraphRequest(keys, request.visitedScreens, request.locale) } else { request } Loading @@ -52,14 +54,14 @@ abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<Str /** * Request of [GetPreferenceGraphApiHandler]. * * @param activityClasses activities of the preference graph * @param screenKeys screen keys of the preference graph * @param visitedScreens keys of the visited preference screen * @param locale locale of the preference graph */ data class GetPreferenceGraphRequest @JvmOverloads constructor( val activityClasses: Set<String> = setOf(), val screenKeys: Set<String> = setOf(), val visitedScreens: Set<String> = setOf(), val locale: Locale? = null, val includeValue: Boolean = true, Loading @@ -68,25 +70,25 @@ constructor( object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> { override fun encode(data: GetPreferenceGraphRequest): Bundle = Bundle(3).apply { putStringArray(KEY_ACTIVITIES, data.activityClasses.toTypedArray()) putStringArray(KEY_PREF_KEYS, data.visitedScreens.toTypedArray()) putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray()) putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray()) putString(KEY_LOCALE, data.locale?.toLanguageTag()) } override fun decode(data: Bundle): GetPreferenceGraphRequest { val activities = data.getStringArray(KEY_ACTIVITIES) ?: arrayOf() val visitedScreens = data.getStringArray(KEY_PREF_KEYS) ?: arrayOf() val screenKeys = data.getStringArray(KEY_SCREEN_KEYS) ?: arrayOf() val visitedScreens = data.getStringArray(KEY_VISITED_KEYS) ?: arrayOf() fun String?.toLocale() = if (this != null) Locale.forLanguageTag(this) else null return GetPreferenceGraphRequest( activities.toSet(), screenKeys.toSet(), visitedScreens.toSet(), data.getString(KEY_LOCALE).toLocale(), ) } private const val KEY_ACTIVITIES = "activities" private const val KEY_PREF_KEYS = "keys" private const val KEY_LOCALE = "locale" private const val KEY_SCREEN_KEYS = "k" private const val KEY_VISITED_KEYS = "v" private const val KEY_LOCALE = "l" } object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> { Loading @@ -96,5 +98,5 @@ object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> { override fun decode(data: Bundle): PreferenceGraphProto = PreferenceGraphProto.parseFrom(data.getByteArray(KEY_GRAPH)!!) private const val KEY_GRAPH = "graph" private const val KEY_GRAPH = "g" } packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +99 −145 Original line number Diff line number Diff line Loading @@ -19,14 +19,12 @@ package com.android.settingslib.graph import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.preference.PreferenceActivity import android.util.Log import androidx.fragment.app.Fragment import androidx.preference.Preference Loading @@ -53,18 +51,13 @@ import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.PreferenceTitleProvider import com.android.settingslib.preference.PreferenceScreenFactory import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.Locale private const val TAG = "PreferenceGraphBuilder" /** * Builder of preference graph. * * Only activity in current application is supported. To create preference graph across * applications, use [crawlPreferenceGraph]. */ /** Builder of preference graph. */ class PreferenceGraphBuilder private constructor(private val context: Context, private val request: GetPreferenceGraphRequest) { private val preferenceScreenFactory by lazy { Loading @@ -75,21 +68,13 @@ private constructor(private val context: Context, private val request: GetPrefer private val includeValue = request.includeValue private suspend fun init() { for (activityClass in request.activityClasses) { add(activityClass) } // Temporarily add all screens for (key in PreferenceScreenRegistry.preferenceScreens.keys) { addPreferenceScreenFromRegistry(key, Activity::class.java) for (key in request.screenKeys) { addPreferenceScreenFromRegistry(key) } } fun build() = builder.build() /** Adds an activity to the graph. */ suspend fun <T> add(activityClass: Class<T>) where T : Activity, T : PreferenceScreenProvider = addPreferenceScreenProvider(activityClass) /** * Adds an activity to the graph. * Loading @@ -100,8 +85,10 @@ private constructor(private val context: Context, private val request: GetPrefer try { val intent = Intent() intent.setClassName(context, activityClassName) if (context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) == null) { if ( context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) == null ) { Log.e(TAG, "$activityClassName is not activity") return } Loading @@ -122,7 +109,7 @@ private constructor(private val context: Context, private val request: GetPrefer return false } val key = getPreferenceScreenKey { activityClass.newInstance() } ?: return false if (addPreferenceScreenFromRegistry(key, activityClass)) { if (addPreferenceScreenFromRegistry(key)) { builder.addRoots(key) return true } Loading @@ -144,23 +131,16 @@ private constructor(private val context: Context, private val request: GetPrefer null } private suspend fun addPreferenceScreenFromRegistry( key: String, activityClass: Class<*>, ): Boolean { private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean { val metadata = PreferenceScreenRegistry[key] ?: return false if (!metadata.hasCompleteHierarchy()) return false return addPreferenceScreenMetadata(metadata, activityClass) return addPreferenceScreenMetadata(metadata) } private suspend fun addPreferenceScreenMetadata( metadata: PreferenceScreenMetadata, activityClass: Class<*>, ): Boolean = addPreferenceScreen(metadata.key, activityClass) { private suspend fun addPreferenceScreenMetadata(metadata: PreferenceScreenMetadata): Boolean = addPreferenceScreen(metadata.key) { preferenceScreenProto { completeHierarchy = true root = metadata.getPreferenceHierarchy(context).toProto(activityClass, true) completeHierarchy = metadata.hasCompleteHierarchy() root = metadata.getPreferenceHierarchy(context).toProto(true) } } Loading @@ -168,7 +148,7 @@ private constructor(private val context: Context, private val request: GetPrefer Log.d(TAG, "add $activityClass") createPreferenceScreen { activityClass.newInstance() } ?.let { addPreferenceScreen(Intent(context, activityClass), activityClass, it) addPreferenceScreen(Intent(context, activityClass), it) builder.addRoots(it.key) } } Loading @@ -195,66 +175,50 @@ private constructor(private val context: Context, private val request: GetPrefer return@withContext null } private suspend fun addPreferenceScreen( intent: Intent, activityClass: Class<*>, preferenceScreen: PreferenceScreen?, ) { private suspend fun addPreferenceScreen(intent: Intent, preferenceScreen: PreferenceScreen?) { val key = preferenceScreen?.key if (key.isNullOrEmpty()) { Log.e(TAG, "$activityClass \"$preferenceScreen\" has no key") Log.e(TAG, "\"$preferenceScreen\" has no key") return } @Suppress("CheckReturnValue") addPreferenceScreen(key, activityClass) { preferenceScreen.toProto(intent, activityClass) } @Suppress("CheckReturnValue") addPreferenceScreen(key) { preferenceScreen.toProto(intent) } } private suspend fun addPreferenceScreen( key: String, activityClass: Class<*>, preferenceScreenProvider: suspend () -> PreferenceScreenProto, ): Boolean { if (!visitedScreens.add(key)) { Log.w(TAG, "$activityClass $key visited") return false } val activityClassName = activityClass.name val associatedKey = builder.getActivityScreensOrDefault(activityClassName, null) if (associatedKey == null) { builder.putActivityScreens(activityClassName, key) } else if (associatedKey != key) { Log.w(TAG, "Dup $activityClassName association, old: $associatedKey, new: $key") } ): Boolean = if (visitedScreens.add(key)) { builder.putScreens(key, preferenceScreenProvider()) return true true } else { Log.w(TAG, "$key visited") false } private suspend fun PreferenceScreen.toProto( intent: Intent, activityClass: Class<*>, ): PreferenceScreenProto = preferenceScreenProto { this.intent = intent.toProto() root = (this@toProto as PreferenceGroup).toProto(activityClass) private suspend fun PreferenceScreen.toProto(intent: Intent?): PreferenceScreenProto = preferenceScreenProto { intent?.let { this.intent = it.toProto() } root = (this@toProto as PreferenceGroup).toProto() } private suspend fun PreferenceGroup.toProto(activityClass: Class<*>): PreferenceGroupProto = preferenceGroupProto { preference = (this@toProto as Preference).toProto(activityClass) private suspend fun PreferenceGroup.toProto(): PreferenceGroupProto = preferenceGroupProto { preference = (this@toProto as Preference).toProto() for (index in 0 until preferenceCount) { val child = getPreference(index) addPreferences( preferenceOrGroupProto { if (child is PreferenceGroup) { group = child.toProto(activityClass) group = child.toProto() } else { preference = child.toProto(activityClass) preference = child.toProto() } }) } ) } } private suspend fun Preference.toProto(activityClass: Class<*>): PreferenceProto = preferenceProto { private suspend fun Preference.toProto(): PreferenceProto = preferenceProto { this@toProto.key?.let { key = it } this@toProto.title?.let { title = textProto { string = it.toString() } } this@toProto.summary?.let { summary = textProto { string = it.toString() } } Loading @@ -266,35 +230,30 @@ private constructor(private val context: Context, private val request: GetPrefer if (includeValue && isPersistent && this@toProto is TwoStatePreference) { value = preferenceValueProto { booleanValue = this@toProto.isChecked } } this@toProto.fragment.toActionTarget(activityClass, preferenceExtras)?.let { this@toProto.fragment.toActionTarget(preferenceExtras)?.let { actionTarget = it return@preferenceProto } this@toProto.intent?.let { actionTarget = it.toActionTarget() } } private suspend fun PreferenceHierarchy.toProto( activityClass: Class<*>, isRoot: Boolean, ): PreferenceGroupProto = preferenceGroupProto { preference = toProto(this@toProto, activityClass, isRoot) private suspend fun PreferenceHierarchy.toProto(isRoot: Boolean): PreferenceGroupProto = preferenceGroupProto { preference = toProto(this@toProto, isRoot) forEachAsync { addPreferences( preferenceOrGroupProto { if (it is PreferenceHierarchy) { group = it.toProto(activityClass, false) group = it.toProto(false) } else { preference = toProto(it, activityClass, false) preference = toProto(it, false) } }) } ) } } private suspend fun toProto( node: PreferenceHierarchyNode, activityClass: Class<*>, isRoot: Boolean, ) = preferenceProto { private suspend fun toProto(node: PreferenceHierarchyNode, isRoot: Boolean) = preferenceProto { val metadata = node.metadata key = metadata.key metadata.getTitleTextProto(isRoot)?.let { title = it } Loading @@ -318,22 +277,18 @@ private constructor(private val context: Context, private val request: GetPrefer restricted = metadata.isRestricted(context) } persistent = metadata.isPersistent(context) if (includeValue && if ( includeValue && persistent && metadata is BooleanValue && metadata is PersistentPreference<*>) { metadata is PersistentPreference<*> ) { metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let { value = preferenceValueProto { booleanValue = it } } } if (metadata is PreferenceScreenMetadata) { if (metadata.hasCompleteHierarchy()) { @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata, activityClass) } else { metadata.fragmentClass()?.toActionTarget(activityClass, preferenceExtras)?.let { actionTarget = it } } @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) } metadata.intent(context)?.let { actionTarget = it.toActionTarget() } } Loading @@ -359,16 +314,13 @@ private constructor(private val context: Context, private val request: GetPrefer } } private suspend fun String?.toActionTarget( activityClass: Class<*>, extras: Bundle?, ): ActionTarget? { private suspend fun String?.toActionTarget(extras: Bundle?): ActionTarget? { if (this.isNullOrEmpty()) return null try { val fragmentClass = context.classLoader.loadClass(this) if (Fragment::class.java.isAssignableFrom(fragmentClass)) { @Suppress("UNCHECKED_CAST") return (fragmentClass as Class<out Fragment>).toActionTarget(activityClass, extras) return (fragmentClass as Class<out Fragment>).toActionTarget(extras) } } catch (e: Exception) { Log.e(TAG, "Cannot loadClass $this", e) Loading @@ -376,16 +328,12 @@ private constructor(private val context: Context, private val request: GetPrefer return null } private suspend fun Class<out Fragment>.toActionTarget( activityClass: Class<*>, extras: Bundle?, ): ActionTarget { val startIntent = Intent(context, activityClass) startIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, name) extras?.let { startIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, it) } if (!PreferenceScreenProvider::class.java.isAssignableFrom(this) && !PreferenceScreenBindingKeyProvider::class.java.isAssignableFrom(this)) { return actionTargetProto { intent = startIntent.toProto() } private suspend fun Class<out Fragment>.toActionTarget(extras: Bundle?): ActionTarget? { if ( !PreferenceScreenProvider::class.java.isAssignableFrom(this) && !PreferenceScreenBindingKeyProvider::class.java.isAssignableFrom(this) ) { return null } val fragment = withContext(Dispatchers.Main) { Loading @@ -398,18 +346,24 @@ private constructor(private val context: Context, private val request: GetPrefer } if (fragment is PreferenceScreenBindingKeyProvider) { val screenKey = fragment.getPreferenceScreenBindingKey(context) if (screenKey != null && addPreferenceScreenFromRegistry(screenKey, activityClass)) { if (screenKey != null && addPreferenceScreenFromRegistry(screenKey)) { return actionTargetProto { key = screenKey } } } if (fragment is PreferenceScreenProvider) { try { val screen = fragment.createPreferenceScreen(preferenceScreenFactory) if (screen != null) { addPreferenceScreen(startIntent, activityClass, screen) return actionTargetProto { key = screen.key } val screenKey = screen?.key if (!screenKey.isNullOrEmpty()) { @Suppress("CheckReturnValue") addPreferenceScreen(screenKey) { screen.toProto(null) } return actionTargetProto { key = screenKey } } } catch (e: Exception) { Log.e(TAG, "Fail to createPreferenceScreen for $fragment", e) } } return actionTargetProto { intent = startIntent.toProto() } return null } private suspend fun Intent.toActionTarget(): ActionTarget { Loading packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt 0 → 100644 +170 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.graph import android.app.Application import android.os.Bundle import androidx.annotation.IntDef import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.ipc.ApiDescriptor import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.IntMessageCodec import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.BooleanValue import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.metadata.ReadWritePermit /** Request to set preference value. */ data class PreferenceSetterRequest( val screenKey: String, val key: String, val value: PreferenceValueProto, ) /** Result of preference setter request. */ @IntDef( PreferenceSetterResult.OK, PreferenceSetterResult.UNSUPPORTED, PreferenceSetterResult.DISABLED, PreferenceSetterResult.UNAVAILABLE, PreferenceSetterResult.INVALID_REQUEST, PreferenceSetterResult.INTERNAL_ERROR, ) @Retention(AnnotationRetention.SOURCE) annotation class PreferenceSetterResult { companion object { /** Set preference value successfully. */ const val OK = 0 /** Set preference value is unsupported on the preference. */ const val UNSUPPORTED = 1 /** Preference is disabled and cannot set preference value. */ const val DISABLED = 2 /** Preference is restricted by managed configuration and cannot set preference value. */ const val RESTRICTED = 3 /** Preference is unavailable and cannot set preference value. */ const val UNAVAILABLE = 4 /** Require (runtime/special) app permission from user explicitly. */ const val REQUIRE_APP_PERMISSION = 5 /** Require explicit user agreement (e.g. terms of service). */ const val REQUIRE_USER_AGREEMENT = 6 /** Disallow to set preference value (e.g. uid not allowed). */ const val DISALLOW = 7 /** Request is invalid. */ const val INVALID_REQUEST = 8 /** Internal error happened when persist preference value. */ const val INTERNAL_ERROR = 9 } } /** Preference setter API descriptor. */ class PreferenceSetterApiDescriptor(override val id: Int) : ApiDescriptor<PreferenceSetterRequest, Int> { override val requestCodec: MessageCodec<PreferenceSetterRequest> get() = PreferenceSetterRequestCodec override val responseCodec: MessageCodec<Int> get() = IntMessageCodec } /** Preference setter API implementation. */ class PreferenceSetterApiHandler(override val id: Int) : ApiHandler<PreferenceSetterRequest, Int> { override fun hasPermission( application: Application, myUid: Int, callingUid: Int, request: PreferenceSetterRequest, ): Boolean = true override suspend fun invoke( application: Application, myUid: Int, callingUid: Int, request: PreferenceSetterRequest, ): Int { val screenMetadata = PreferenceScreenRegistry[request.screenKey] ?: return PreferenceSetterResult.UNSUPPORTED val key = request.key val metadata = screenMetadata.getPreferenceHierarchy(application).find(key) ?: return PreferenceSetterResult.UNSUPPORTED if (metadata !is PersistentPreference<*>) return PreferenceSetterResult.UNSUPPORTED if (!metadata.isEnabled(application)) return PreferenceSetterResult.DISABLED if (metadata is PreferenceRestrictionProvider && metadata.isRestricted(application)) { return PreferenceSetterResult.RESTRICTED } if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) { return PreferenceSetterResult.UNAVAILABLE } val storage = metadata.storage(application) val value = request.value try { if (value.hasBooleanValue()) { if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST val booleanValue = value.booleanValue @Suppress("UNCHECKED_CAST") val booleanPreference = metadata as PersistentPreference<Boolean> when ( booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid) ) { ReadWritePermit.ALLOW -> {} ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW ReadWritePermit.REQUIRE_APP_PERMISSION -> return PreferenceSetterResult.REQUIRE_APP_PERMISSION ReadWritePermit.REQUIRE_USER_AGREEMENT -> return PreferenceSetterResult.REQUIRE_USER_AGREEMENT else -> return PreferenceSetterResult.INTERNAL_ERROR } storage.setValue(key, Boolean::class.javaObjectType, booleanValue) return PreferenceSetterResult.OK } } catch (e: Exception) { return PreferenceSetterResult.INTERNAL_ERROR } return PreferenceSetterResult.INVALID_REQUEST } override val requestCodec: MessageCodec<PreferenceSetterRequest> get() = PreferenceSetterRequestCodec override val responseCodec: MessageCodec<Int> get() = IntMessageCodec } /** Message codec for [PreferenceSetterRequest]. */ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> { override fun encode(data: PreferenceSetterRequest) = Bundle(3).apply { putString(SCREEN_KEY, data.screenKey) putString(KEY, data.key) putByteArray(null, data.value.toByteArray()) } override fun decode(data: Bundle) = PreferenceSetterRequest( data.getString(SCREEN_KEY)!!, data.getString(KEY)!!, PreferenceValueProto.parseFrom(data.getByteArray(null)!!), ) private const val SCREEN_KEY = "s" private const val KEY = "k" } packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt +3 −5 Original line number Diff line number Diff line Loading @@ -21,8 +21,8 @@ import com.android.settingslib.graph.GetPreferenceGraphApiHandler import com.android.settingslib.graph.GetPreferenceGraphRequest /** Api to get preference graph. */ internal class PreferenceGraphApi(activityClasses: Set<String>) : GetPreferenceGraphApiHandler(activityClasses) { internal class PreferenceGraphApi : GetPreferenceGraphApiHandler() { override val id: Int get() = API_GET_PREFERENCE_GRAPH Loading @@ -31,7 +31,5 @@ internal class PreferenceGraphApi(activityClasses: Set<String>) : myUid: Int, callingUid: Int, request: GetPreferenceGraphRequest, ): Boolean { return true // TODO: add permission check } ) = true } packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt +5 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +16 −14 Original line number Diff line number Diff line Loading @@ -21,10 +21,11 @@ import android.os.Bundle import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.PreferenceScreenRegistry import java.util.Locale /** API to get preference graph. */ abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<String>) : abstract class GetPreferenceGraphApiHandler : ApiHandler<GetPreferenceGraphRequest, PreferenceGraphProto> { override val requestCodec: MessageCodec<GetPreferenceGraphRequest> Loading @@ -40,8 +41,9 @@ abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<Str request: GetPreferenceGraphRequest, ): PreferenceGraphProto { val builderRequest = if (request.activityClasses.isEmpty()) { GetPreferenceGraphRequest(activityClasses, request.visitedScreens, request.locale) if (request.screenKeys.isEmpty()) { val keys = PreferenceScreenRegistry.preferenceScreens.keys GetPreferenceGraphRequest(keys, request.visitedScreens, request.locale) } else { request } Loading @@ -52,14 +54,14 @@ abstract class GetPreferenceGraphApiHandler(private val activityClasses: Set<Str /** * Request of [GetPreferenceGraphApiHandler]. * * @param activityClasses activities of the preference graph * @param screenKeys screen keys of the preference graph * @param visitedScreens keys of the visited preference screen * @param locale locale of the preference graph */ data class GetPreferenceGraphRequest @JvmOverloads constructor( val activityClasses: Set<String> = setOf(), val screenKeys: Set<String> = setOf(), val visitedScreens: Set<String> = setOf(), val locale: Locale? = null, val includeValue: Boolean = true, Loading @@ -68,25 +70,25 @@ constructor( object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> { override fun encode(data: GetPreferenceGraphRequest): Bundle = Bundle(3).apply { putStringArray(KEY_ACTIVITIES, data.activityClasses.toTypedArray()) putStringArray(KEY_PREF_KEYS, data.visitedScreens.toTypedArray()) putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray()) putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray()) putString(KEY_LOCALE, data.locale?.toLanguageTag()) } override fun decode(data: Bundle): GetPreferenceGraphRequest { val activities = data.getStringArray(KEY_ACTIVITIES) ?: arrayOf() val visitedScreens = data.getStringArray(KEY_PREF_KEYS) ?: arrayOf() val screenKeys = data.getStringArray(KEY_SCREEN_KEYS) ?: arrayOf() val visitedScreens = data.getStringArray(KEY_VISITED_KEYS) ?: arrayOf() fun String?.toLocale() = if (this != null) Locale.forLanguageTag(this) else null return GetPreferenceGraphRequest( activities.toSet(), screenKeys.toSet(), visitedScreens.toSet(), data.getString(KEY_LOCALE).toLocale(), ) } private const val KEY_ACTIVITIES = "activities" private const val KEY_PREF_KEYS = "keys" private const val KEY_LOCALE = "locale" private const val KEY_SCREEN_KEYS = "k" private const val KEY_VISITED_KEYS = "v" private const val KEY_LOCALE = "l" } object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> { Loading @@ -96,5 +98,5 @@ object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> { override fun decode(data: Bundle): PreferenceGraphProto = PreferenceGraphProto.parseFrom(data.getByteArray(KEY_GRAPH)!!) private const val KEY_GRAPH = "graph" private const val KEY_GRAPH = "g" }
packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +99 −145 Original line number Diff line number Diff line Loading @@ -19,14 +19,12 @@ package com.android.settingslib.graph import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.preference.PreferenceActivity import android.util.Log import androidx.fragment.app.Fragment import androidx.preference.Preference Loading @@ -53,18 +51,13 @@ import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.PreferenceTitleProvider import com.android.settingslib.preference.PreferenceScreenFactory import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.Locale private const val TAG = "PreferenceGraphBuilder" /** * Builder of preference graph. * * Only activity in current application is supported. To create preference graph across * applications, use [crawlPreferenceGraph]. */ /** Builder of preference graph. */ class PreferenceGraphBuilder private constructor(private val context: Context, private val request: GetPreferenceGraphRequest) { private val preferenceScreenFactory by lazy { Loading @@ -75,21 +68,13 @@ private constructor(private val context: Context, private val request: GetPrefer private val includeValue = request.includeValue private suspend fun init() { for (activityClass in request.activityClasses) { add(activityClass) } // Temporarily add all screens for (key in PreferenceScreenRegistry.preferenceScreens.keys) { addPreferenceScreenFromRegistry(key, Activity::class.java) for (key in request.screenKeys) { addPreferenceScreenFromRegistry(key) } } fun build() = builder.build() /** Adds an activity to the graph. */ suspend fun <T> add(activityClass: Class<T>) where T : Activity, T : PreferenceScreenProvider = addPreferenceScreenProvider(activityClass) /** * Adds an activity to the graph. * Loading @@ -100,8 +85,10 @@ private constructor(private val context: Context, private val request: GetPrefer try { val intent = Intent() intent.setClassName(context, activityClassName) if (context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) == null) { if ( context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) == null ) { Log.e(TAG, "$activityClassName is not activity") return } Loading @@ -122,7 +109,7 @@ private constructor(private val context: Context, private val request: GetPrefer return false } val key = getPreferenceScreenKey { activityClass.newInstance() } ?: return false if (addPreferenceScreenFromRegistry(key, activityClass)) { if (addPreferenceScreenFromRegistry(key)) { builder.addRoots(key) return true } Loading @@ -144,23 +131,16 @@ private constructor(private val context: Context, private val request: GetPrefer null } private suspend fun addPreferenceScreenFromRegistry( key: String, activityClass: Class<*>, ): Boolean { private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean { val metadata = PreferenceScreenRegistry[key] ?: return false if (!metadata.hasCompleteHierarchy()) return false return addPreferenceScreenMetadata(metadata, activityClass) return addPreferenceScreenMetadata(metadata) } private suspend fun addPreferenceScreenMetadata( metadata: PreferenceScreenMetadata, activityClass: Class<*>, ): Boolean = addPreferenceScreen(metadata.key, activityClass) { private suspend fun addPreferenceScreenMetadata(metadata: PreferenceScreenMetadata): Boolean = addPreferenceScreen(metadata.key) { preferenceScreenProto { completeHierarchy = true root = metadata.getPreferenceHierarchy(context).toProto(activityClass, true) completeHierarchy = metadata.hasCompleteHierarchy() root = metadata.getPreferenceHierarchy(context).toProto(true) } } Loading @@ -168,7 +148,7 @@ private constructor(private val context: Context, private val request: GetPrefer Log.d(TAG, "add $activityClass") createPreferenceScreen { activityClass.newInstance() } ?.let { addPreferenceScreen(Intent(context, activityClass), activityClass, it) addPreferenceScreen(Intent(context, activityClass), it) builder.addRoots(it.key) } } Loading @@ -195,66 +175,50 @@ private constructor(private val context: Context, private val request: GetPrefer return@withContext null } private suspend fun addPreferenceScreen( intent: Intent, activityClass: Class<*>, preferenceScreen: PreferenceScreen?, ) { private suspend fun addPreferenceScreen(intent: Intent, preferenceScreen: PreferenceScreen?) { val key = preferenceScreen?.key if (key.isNullOrEmpty()) { Log.e(TAG, "$activityClass \"$preferenceScreen\" has no key") Log.e(TAG, "\"$preferenceScreen\" has no key") return } @Suppress("CheckReturnValue") addPreferenceScreen(key, activityClass) { preferenceScreen.toProto(intent, activityClass) } @Suppress("CheckReturnValue") addPreferenceScreen(key) { preferenceScreen.toProto(intent) } } private suspend fun addPreferenceScreen( key: String, activityClass: Class<*>, preferenceScreenProvider: suspend () -> PreferenceScreenProto, ): Boolean { if (!visitedScreens.add(key)) { Log.w(TAG, "$activityClass $key visited") return false } val activityClassName = activityClass.name val associatedKey = builder.getActivityScreensOrDefault(activityClassName, null) if (associatedKey == null) { builder.putActivityScreens(activityClassName, key) } else if (associatedKey != key) { Log.w(TAG, "Dup $activityClassName association, old: $associatedKey, new: $key") } ): Boolean = if (visitedScreens.add(key)) { builder.putScreens(key, preferenceScreenProvider()) return true true } else { Log.w(TAG, "$key visited") false } private suspend fun PreferenceScreen.toProto( intent: Intent, activityClass: Class<*>, ): PreferenceScreenProto = preferenceScreenProto { this.intent = intent.toProto() root = (this@toProto as PreferenceGroup).toProto(activityClass) private suspend fun PreferenceScreen.toProto(intent: Intent?): PreferenceScreenProto = preferenceScreenProto { intent?.let { this.intent = it.toProto() } root = (this@toProto as PreferenceGroup).toProto() } private suspend fun PreferenceGroup.toProto(activityClass: Class<*>): PreferenceGroupProto = preferenceGroupProto { preference = (this@toProto as Preference).toProto(activityClass) private suspend fun PreferenceGroup.toProto(): PreferenceGroupProto = preferenceGroupProto { preference = (this@toProto as Preference).toProto() for (index in 0 until preferenceCount) { val child = getPreference(index) addPreferences( preferenceOrGroupProto { if (child is PreferenceGroup) { group = child.toProto(activityClass) group = child.toProto() } else { preference = child.toProto(activityClass) preference = child.toProto() } }) } ) } } private suspend fun Preference.toProto(activityClass: Class<*>): PreferenceProto = preferenceProto { private suspend fun Preference.toProto(): PreferenceProto = preferenceProto { this@toProto.key?.let { key = it } this@toProto.title?.let { title = textProto { string = it.toString() } } this@toProto.summary?.let { summary = textProto { string = it.toString() } } Loading @@ -266,35 +230,30 @@ private constructor(private val context: Context, private val request: GetPrefer if (includeValue && isPersistent && this@toProto is TwoStatePreference) { value = preferenceValueProto { booleanValue = this@toProto.isChecked } } this@toProto.fragment.toActionTarget(activityClass, preferenceExtras)?.let { this@toProto.fragment.toActionTarget(preferenceExtras)?.let { actionTarget = it return@preferenceProto } this@toProto.intent?.let { actionTarget = it.toActionTarget() } } private suspend fun PreferenceHierarchy.toProto( activityClass: Class<*>, isRoot: Boolean, ): PreferenceGroupProto = preferenceGroupProto { preference = toProto(this@toProto, activityClass, isRoot) private suspend fun PreferenceHierarchy.toProto(isRoot: Boolean): PreferenceGroupProto = preferenceGroupProto { preference = toProto(this@toProto, isRoot) forEachAsync { addPreferences( preferenceOrGroupProto { if (it is PreferenceHierarchy) { group = it.toProto(activityClass, false) group = it.toProto(false) } else { preference = toProto(it, activityClass, false) preference = toProto(it, false) } }) } ) } } private suspend fun toProto( node: PreferenceHierarchyNode, activityClass: Class<*>, isRoot: Boolean, ) = preferenceProto { private suspend fun toProto(node: PreferenceHierarchyNode, isRoot: Boolean) = preferenceProto { val metadata = node.metadata key = metadata.key metadata.getTitleTextProto(isRoot)?.let { title = it } Loading @@ -318,22 +277,18 @@ private constructor(private val context: Context, private val request: GetPrefer restricted = metadata.isRestricted(context) } persistent = metadata.isPersistent(context) if (includeValue && if ( includeValue && persistent && metadata is BooleanValue && metadata is PersistentPreference<*>) { metadata is PersistentPreference<*> ) { metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let { value = preferenceValueProto { booleanValue = it } } } if (metadata is PreferenceScreenMetadata) { if (metadata.hasCompleteHierarchy()) { @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata, activityClass) } else { metadata.fragmentClass()?.toActionTarget(activityClass, preferenceExtras)?.let { actionTarget = it } } @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) } metadata.intent(context)?.let { actionTarget = it.toActionTarget() } } Loading @@ -359,16 +314,13 @@ private constructor(private val context: Context, private val request: GetPrefer } } private suspend fun String?.toActionTarget( activityClass: Class<*>, extras: Bundle?, ): ActionTarget? { private suspend fun String?.toActionTarget(extras: Bundle?): ActionTarget? { if (this.isNullOrEmpty()) return null try { val fragmentClass = context.classLoader.loadClass(this) if (Fragment::class.java.isAssignableFrom(fragmentClass)) { @Suppress("UNCHECKED_CAST") return (fragmentClass as Class<out Fragment>).toActionTarget(activityClass, extras) return (fragmentClass as Class<out Fragment>).toActionTarget(extras) } } catch (e: Exception) { Log.e(TAG, "Cannot loadClass $this", e) Loading @@ -376,16 +328,12 @@ private constructor(private val context: Context, private val request: GetPrefer return null } private suspend fun Class<out Fragment>.toActionTarget( activityClass: Class<*>, extras: Bundle?, ): ActionTarget { val startIntent = Intent(context, activityClass) startIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, name) extras?.let { startIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, it) } if (!PreferenceScreenProvider::class.java.isAssignableFrom(this) && !PreferenceScreenBindingKeyProvider::class.java.isAssignableFrom(this)) { return actionTargetProto { intent = startIntent.toProto() } private suspend fun Class<out Fragment>.toActionTarget(extras: Bundle?): ActionTarget? { if ( !PreferenceScreenProvider::class.java.isAssignableFrom(this) && !PreferenceScreenBindingKeyProvider::class.java.isAssignableFrom(this) ) { return null } val fragment = withContext(Dispatchers.Main) { Loading @@ -398,18 +346,24 @@ private constructor(private val context: Context, private val request: GetPrefer } if (fragment is PreferenceScreenBindingKeyProvider) { val screenKey = fragment.getPreferenceScreenBindingKey(context) if (screenKey != null && addPreferenceScreenFromRegistry(screenKey, activityClass)) { if (screenKey != null && addPreferenceScreenFromRegistry(screenKey)) { return actionTargetProto { key = screenKey } } } if (fragment is PreferenceScreenProvider) { try { val screen = fragment.createPreferenceScreen(preferenceScreenFactory) if (screen != null) { addPreferenceScreen(startIntent, activityClass, screen) return actionTargetProto { key = screen.key } val screenKey = screen?.key if (!screenKey.isNullOrEmpty()) { @Suppress("CheckReturnValue") addPreferenceScreen(screenKey) { screen.toProto(null) } return actionTargetProto { key = screenKey } } } catch (e: Exception) { Log.e(TAG, "Fail to createPreferenceScreen for $fragment", e) } } return actionTargetProto { intent = startIntent.toProto() } return null } private suspend fun Intent.toActionTarget(): ActionTarget { Loading
packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt 0 → 100644 +170 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.graph import android.app.Application import android.os.Bundle import androidx.annotation.IntDef import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.ipc.ApiDescriptor import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.IntMessageCodec import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.BooleanValue import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.metadata.ReadWritePermit /** Request to set preference value. */ data class PreferenceSetterRequest( val screenKey: String, val key: String, val value: PreferenceValueProto, ) /** Result of preference setter request. */ @IntDef( PreferenceSetterResult.OK, PreferenceSetterResult.UNSUPPORTED, PreferenceSetterResult.DISABLED, PreferenceSetterResult.UNAVAILABLE, PreferenceSetterResult.INVALID_REQUEST, PreferenceSetterResult.INTERNAL_ERROR, ) @Retention(AnnotationRetention.SOURCE) annotation class PreferenceSetterResult { companion object { /** Set preference value successfully. */ const val OK = 0 /** Set preference value is unsupported on the preference. */ const val UNSUPPORTED = 1 /** Preference is disabled and cannot set preference value. */ const val DISABLED = 2 /** Preference is restricted by managed configuration and cannot set preference value. */ const val RESTRICTED = 3 /** Preference is unavailable and cannot set preference value. */ const val UNAVAILABLE = 4 /** Require (runtime/special) app permission from user explicitly. */ const val REQUIRE_APP_PERMISSION = 5 /** Require explicit user agreement (e.g. terms of service). */ const val REQUIRE_USER_AGREEMENT = 6 /** Disallow to set preference value (e.g. uid not allowed). */ const val DISALLOW = 7 /** Request is invalid. */ const val INVALID_REQUEST = 8 /** Internal error happened when persist preference value. */ const val INTERNAL_ERROR = 9 } } /** Preference setter API descriptor. */ class PreferenceSetterApiDescriptor(override val id: Int) : ApiDescriptor<PreferenceSetterRequest, Int> { override val requestCodec: MessageCodec<PreferenceSetterRequest> get() = PreferenceSetterRequestCodec override val responseCodec: MessageCodec<Int> get() = IntMessageCodec } /** Preference setter API implementation. */ class PreferenceSetterApiHandler(override val id: Int) : ApiHandler<PreferenceSetterRequest, Int> { override fun hasPermission( application: Application, myUid: Int, callingUid: Int, request: PreferenceSetterRequest, ): Boolean = true override suspend fun invoke( application: Application, myUid: Int, callingUid: Int, request: PreferenceSetterRequest, ): Int { val screenMetadata = PreferenceScreenRegistry[request.screenKey] ?: return PreferenceSetterResult.UNSUPPORTED val key = request.key val metadata = screenMetadata.getPreferenceHierarchy(application).find(key) ?: return PreferenceSetterResult.UNSUPPORTED if (metadata !is PersistentPreference<*>) return PreferenceSetterResult.UNSUPPORTED if (!metadata.isEnabled(application)) return PreferenceSetterResult.DISABLED if (metadata is PreferenceRestrictionProvider && metadata.isRestricted(application)) { return PreferenceSetterResult.RESTRICTED } if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) { return PreferenceSetterResult.UNAVAILABLE } val storage = metadata.storage(application) val value = request.value try { if (value.hasBooleanValue()) { if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST val booleanValue = value.booleanValue @Suppress("UNCHECKED_CAST") val booleanPreference = metadata as PersistentPreference<Boolean> when ( booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid) ) { ReadWritePermit.ALLOW -> {} ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW ReadWritePermit.REQUIRE_APP_PERMISSION -> return PreferenceSetterResult.REQUIRE_APP_PERMISSION ReadWritePermit.REQUIRE_USER_AGREEMENT -> return PreferenceSetterResult.REQUIRE_USER_AGREEMENT else -> return PreferenceSetterResult.INTERNAL_ERROR } storage.setValue(key, Boolean::class.javaObjectType, booleanValue) return PreferenceSetterResult.OK } } catch (e: Exception) { return PreferenceSetterResult.INTERNAL_ERROR } return PreferenceSetterResult.INVALID_REQUEST } override val requestCodec: MessageCodec<PreferenceSetterRequest> get() = PreferenceSetterRequestCodec override val responseCodec: MessageCodec<Int> get() = IntMessageCodec } /** Message codec for [PreferenceSetterRequest]. */ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> { override fun encode(data: PreferenceSetterRequest) = Bundle(3).apply { putString(SCREEN_KEY, data.screenKey) putString(KEY, data.key) putByteArray(null, data.value.toByteArray()) } override fun decode(data: Bundle) = PreferenceSetterRequest( data.getString(SCREEN_KEY)!!, data.getString(KEY)!!, PreferenceValueProto.parseFrom(data.getByteArray(null)!!), ) private const val SCREEN_KEY = "s" private const val KEY = "k" }
packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt +3 −5 Original line number Diff line number Diff line Loading @@ -21,8 +21,8 @@ import com.android.settingslib.graph.GetPreferenceGraphApiHandler import com.android.settingslib.graph.GetPreferenceGraphRequest /** Api to get preference graph. */ internal class PreferenceGraphApi(activityClasses: Set<String>) : GetPreferenceGraphApiHandler(activityClasses) { internal class PreferenceGraphApi : GetPreferenceGraphApiHandler() { override val id: Int get() = API_GET_PREFERENCE_GRAPH Loading @@ -31,7 +31,5 @@ internal class PreferenceGraphApi(activityClasses: Set<String>) : myUid: Int, callingUid: Int, request: GetPreferenceGraphRequest, ): Boolean { return true // TODO: add permission check } ) = true }
packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt +5 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes