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

Commit 8927e437 authored by Chris Antol's avatar Chris Antol
Browse files

Support GetMetadata for Preference Service

Bug: 379750656
Flag: com.android.settingslib.flags.settings_catalyst
Test: unit test
Change-Id: Ia9b438360b60ff509a259df0a079ec4d745fb595
parent 1a4de937
Loading
Loading
Loading
Loading
+30 −8
Original line number Diff line number Diff line
@@ -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<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 @@ class PreferenceService : SettingsPreferenceService() {
        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 @@ 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
    }
}
+52 −0
Original line number Diff line number Diff line
@@ -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<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 @@ 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
+74 −1
Original line number Diff line number Diff line
@@ -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()