From 8927e4371d54361fec3c150c4d6548886588069c Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Wed, 11 Dec 2024 23:36:12 +0000 Subject: [PATCH] Support GetMetadata for Preference Service Bug: 379750656 Flag: com.android.settingslib.flags.settings_catalyst Test: unit test Change-Id: Ia9b438360b60ff509a259df0a079ec4d745fb595 --- .../settings/service/PreferenceService.kt | 38 ++++++++-- .../PreferenceServiceRequestTransformer.kt | 52 +++++++++++++ ...PreferenceServiceRequestTransformerTest.kt | 75 ++++++++++++++++++- 3 files changed, 156 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt index 3a677629afd..d07e78d333e 100644 --- a/src/com/android/settings/service/PreferenceService.kt +++ b/src/com/android/settings/service/PreferenceService.kt @@ -16,6 +16,7 @@ package com.android.settings.service +import android.app.Application import android.os.Binder import android.os.OutcomeReceiver import android.os.Process @@ -26,38 +27,49 @@ import android.service.settings.preferences.MetadataResult import android.service.settings.preferences.SetValueRequest import android.service.settings.preferences.SetValueResult import android.service.settings.preferences.SettingsPreferenceService +import com.android.settingslib.graph.GetPreferenceGraphApiHandler +import com.android.settingslib.graph.GetPreferenceGraphRequest import com.android.settingslib.graph.PreferenceGetterApiHandler +import com.android.settingslib.graph.PreferenceGetterFlags import com.android.settingslib.graph.PreferenceSetterApiHandler import com.android.settingslib.ipc.ApiPermissionChecker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.lang.Exception class PreferenceService : SettingsPreferenceService() { - private val scope = CoroutineScope(Job() + Dispatchers.Main) + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val getApiHandler = PreferenceGetterApiHandler(1, ApiPermissionChecker.alwaysAllow()) private val setApiHandler = PreferenceSetterApiHandler(2, ApiPermissionChecker.alwaysAllow()) + private val graphApi = GraphProvider(3) override fun onGetAllPreferenceMetadata( request: MetadataRequest, callback: OutcomeReceiver ) { - // TODO(379750656): Update graph API to be usable outside SettingsLib - callback.onError(UnsupportedOperationException("Not yet supported")) + scope.launch { + val graphProto = graphApi.invoke(application, Process.myUid(), Binder.getCallingUid(), + GetPreferenceGraphRequest( + includeValue = false, + flags = PreferenceGetterFlags.METADATA + )) + val result = transformCatalystGetMetadataResponse(this@PreferenceService, graphProto) + callback.onResult(result) + } } override fun onGetPreferenceValue( request: GetValueRequest, callback: OutcomeReceiver ) { - scope.launch(Dispatchers.IO) { + scope.launch { val apiRequest = transformFrameworkGetValueRequest(request) val response = getApiHandler.invoke(application, Process.myUid(), - Binder.getCallingPid(), apiRequest) + Binder.getCallingUid(), apiRequest) val result = transformCatalystGetValueResponse( this@PreferenceService, request, @@ -75,7 +87,7 @@ class PreferenceService : SettingsPreferenceService() { request: SetValueRequest, callback: OutcomeReceiver ) { - scope.launch(Dispatchers.IO) { + scope.launch { val apiRequest = transformFrameworkSetValueRequest(request) if (apiRequest == null) { callback.onResult( @@ -83,10 +95,20 @@ class PreferenceService : SettingsPreferenceService() { ) } else { val response = setApiHandler.invoke(application, Process.myUid(), - Binder.getCallingPid(), apiRequest) + Binder.getCallingUid(), apiRequest) callback.onResult(transformCatalystSetValueResponse(response)) } } } + + // Basic implementation - we already have permission to access Graph for Metadata via superclass + private class GraphProvider(override val id: Int) : GetPreferenceGraphApiHandler(emptySet()) { + override fun hasPermission( + application: Application, + myUid: Int, + callingUid: Int, + request: GetPreferenceGraphRequest + ) = true + } } diff --git a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt index 7a4c7fc9dff..18307e0baf3 100644 --- a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt +++ b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt @@ -19,6 +19,7 @@ package com.android.settings.service import android.content.Context import android.service.settings.preferences.GetValueRequest import android.service.settings.preferences.GetValueResult +import android.service.settings.preferences.MetadataResult import android.service.settings.preferences.SetValueRequest import android.service.settings.preferences.SetValueResult import android.service.settings.preferences.SettingsPreferenceMetadata @@ -34,9 +35,55 @@ import com.android.settingslib.graph.preferenceValueProto import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.graph.getText +import com.android.settingslib.graph.proto.PreferenceGraphProto +import com.android.settingslib.graph.proto.PreferenceOrGroupProto import com.android.settingslib.graph.toIntent import com.android.settingslib.metadata.SensitivityLevel +/** Transform Catalyst Graph result to Framework GET METADATA result */ +fun transformCatalystGetMetadataResponse( + context: Context, + graph: PreferenceGraphProto +): MetadataResult { + val preferences = mutableSetOf() + // recursive function to visit all nodes in preference group + fun traverseGroupOrPref( + screenKey: String, + groupOrPref: PreferenceOrGroupProto, + ) { + when (groupOrPref.kindCase) { + PreferenceOrGroupProto.KindCase.PREFERENCE -> + preferences.add( + PreferenceWithScreen(screenKey, groupOrPref.preference) + ) + PreferenceOrGroupProto.KindCase.GROUP -> { + for (child in groupOrPref.group.preferencesList) { + traverseGroupOrPref(screenKey, child) + } + } + else -> {} + } + } + // traverse all screens and all preferences on screen + for ((screenKey, screen) in graph.screensMap) { + for (groupOrPref in screen.root.preferencesList) { + traverseGroupOrPref(screenKey, groupOrPref) + } + } + + return if (preferences.isNotEmpty()) { + MetadataResult.Builder(MetadataResult.RESULT_OK) + .setMetadataList( + preferences.map { + it.preference.toMetadata(context, it.screenKey) + } + ) + .build() + } else { + MetadataResult.Builder(MetadataResult.RESULT_UNSUPPORTED).build() + } +} + /** Translate Framework GET VALUE request to Catalyst GET VALUE request */ fun transformFrameworkGetValueRequest( request: GetValueRequest, @@ -133,6 +180,11 @@ fun transformCatalystSetValueResponse(@PreferenceSetterResult response: Int): Se return SetValueResult.Builder(resultCode).build() } +private data class PreferenceWithScreen( + val screenKey: String, + val preference: PreferenceProto, +) + private fun PreferenceProto.toMetadata( context: Context, screenKey: String diff --git a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt index f064b221282..7631a00e9d1 100644 --- a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt +++ b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt @@ -16,7 +16,6 @@ package com.android.settings.service -import android.content.ComponentName import android.content.Context import android.content.Intent import android.platform.test.annotations.RequiresFlagsEnabled @@ -24,6 +23,7 @@ import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.service.settings.preferences.GetValueRequest import android.service.settings.preferences.GetValueResult +import android.service.settings.preferences.MetadataResult import android.service.settings.preferences.SetValueRequest import android.service.settings.preferences.SetValueResult import android.service.settings.preferences.SettingsPreferenceMetadata @@ -37,9 +37,15 @@ import com.android.settingslib.graph.PreferenceGetterErrorCode import com.android.settingslib.graph.PreferenceGetterFlags import com.android.settingslib.graph.PreferenceGetterResponse import com.android.settingslib.graph.PreferenceSetterResult +import com.android.settingslib.graph.preferenceGroupProto +import com.android.settingslib.graph.preferenceOrGroupProto +import com.android.settingslib.graph.preferenceProto +import com.android.settingslib.graph.preferenceScreenProto +import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.graph.proto.TextProto +import com.android.settingslib.graph.textProto import com.android.settingslib.graph.toProto import com.android.settingslib.metadata.SensitivityLevel import com.google.common.truth.Truth.assertThat @@ -54,6 +60,73 @@ class PreferenceServiceRequestTransformerTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @Test + fun transformCatalystGetMetadataResponse_emptyGraph_returnsFrameworkResponseWithError() { + val context: Context = ApplicationProvider.getApplicationContext() + val graphProto = PreferenceGraphProto.newBuilder().build() + val fResult = transformCatalystGetMetadataResponse(context, graphProto) + with(fResult) { + assertThat(resultCode).isEqualTo(MetadataResult.RESULT_UNSUPPORTED) + assertThat(metadataList).isEmpty() + } + } + + @Test + fun transformCatalystGetMetadataResponse_populatedGraph_returnsFrameworkResponseWithSuccess() { + val context: Context = ApplicationProvider.getApplicationContext() + val screen = preferenceScreenProto { + root = preferenceGroupProto { + addAllPreferences( + listOf( + preferenceOrGroupProto { + group = preferenceGroupProto { + addPreferences( + preferenceOrGroupProto { + preference = preferenceProto { + key = "key1" + title = textProto { string = "title1" } + enabled = true + } + } + ) + } + }, + preferenceOrGroupProto { + preference = preferenceProto { + key = "key2" + title = textProto { string = "title2" } + enabled = false + } + } + ) + ) + } + } + val graphProto = PreferenceGraphProto.newBuilder().putScreens("screen", screen).build() + + val fResult = transformCatalystGetMetadataResponse(context, graphProto) + with(fResult) { + assertThat(resultCode).isEqualTo(MetadataResult.RESULT_OK) + assertThat(metadataList.size).isEqualTo(2) + } + assertThat( + fResult.metadataList.any { + it.key == "key1" && + it.screenKey == "screen" && + it.title == "title1" && + it.isEnabled == true + } + ).isTrue() + assertThat( + fResult.metadataList.any { + it.key == "key2" && + it.screenKey == "screen" && + it.title == "title2" && + it.isEnabled == false + } + ).isTrue() + } + @Test fun transformFrameworkGetValueRequest_returnsValidCatalystRequest() { val fRequest = GetValueRequest.Builder("screen", "pref").build()