Support GetMetadata for Preference Service
Bug: 379750656
Flag: com.android.settingslib.flags.settings_catalyst
Test: unit test
Change-Id: Ia9b438360b60ff509a259df0a079ec4d745fb595
diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt
index 3a67762..d07e78d 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.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<MetadataResult, Exception>
) {
- // 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<GetValueResult, Exception>
) {
- 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 @@
request: SetValueRequest,
callback: OutcomeReceiver<SetValueResult, Exception>
) {
- scope.launch(Dispatchers.IO) {
+ scope.launch {
val apiRequest = transformFrameworkSetValueRequest(request)
if (apiRequest == null) {
callback.onResult(
@@ -83,10 +95,20 @@
)
} 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 7a4c7fc..18307e0 100644
--- a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
+++ b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
@@ -19,6 +19,7 @@
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.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<PreferenceWithScreen>()
+ // 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 @@
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 f064b22..7631a00 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.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.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
@@ -55,6 +61,73 @@
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()
val cRequest = transformFrameworkGetValueRequest(fRequest)