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

Commit 26474cbd authored by Jacky Wang's avatar Jacky Wang
Browse files

[Catalyst] Provide preference Getter API

Batch GET is supported.

Bug: 373895596
Flag: EXEMPT library
Test: manual
Change-Id: I89e33e5300ffac16e4e0bc9545ae424f91c826c1
parent f411ed37
Loading
Loading
Loading
Loading
+45 −0
Original line number Original line 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.os.Parcel
import android.os.Parcelable

/**
 * Coordinate to locate a preference.
 *
 * Within an app, the preference screen key (unique among screens) plus preference key (unique on
 * the screen) is used to locate a preference.
 */
data class PreferenceCoordinate(val screenKey: String, val key: String) : Parcelable {

    constructor(parcel: Parcel) : this(parcel.readString()!!, parcel.readString()!!)

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(screenKey)
        parcel.writeString(key)
    }

    override fun describeContents() = 0

    companion object CREATOR : Parcelable.Creator<PreferenceCoordinate> {

        override fun createFromParcel(parcel: Parcel) = PreferenceCoordinate(parcel)

        override fun newArray(size: Int) = arrayOfNulls<PreferenceCoordinate>(size)
    }
}
+148 −0
Original line number Original line 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 androidx.annotation.IntDef
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.ipc.ApiDescriptor
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.metadata.PreferenceHierarchyNode
import com.android.settingslib.metadata.PreferenceScreenRegistry

/**
 * Request to get preference information.
 *
 * @param preferences coordinate of preferences
 * @param flags a combination of constants in [PreferenceGetterFlags]
 */
class PreferenceGetterRequest(val preferences: Array<PreferenceCoordinate>, val flags: Int)

/** Error code of preference getter request. */
@Target(AnnotationTarget.TYPE)
@IntDef(
    PreferenceGetterErrorCode.NOT_FOUND,
    PreferenceGetterErrorCode.DISALLOW,
    PreferenceGetterErrorCode.INTERNAL_ERROR,
)
@Retention(AnnotationRetention.SOURCE)
annotation class PreferenceGetterErrorCode {
    companion object {
        /** Preference is not found. */
        const val NOT_FOUND = 1
        /** Disallow to get preference value (e.g. uid not allowed). */
        const val DISALLOW = 2
        /** Internal error happened when get preference information. */
        const val INTERNAL_ERROR = 3

        fun getMessage(code: Int) =
            when (code) {
                NOT_FOUND -> "Preference not found"
                DISALLOW -> "Disallow to get preference value"
                INTERNAL_ERROR -> "Internal error"
                else -> "Unknown error"
            }
    }
}

/** Response of the getter API. */
class PreferenceGetterResponse(
    val errors: Map<PreferenceCoordinate, @PreferenceGetterErrorCode Int>,
    val preferences: Map<PreferenceCoordinate, PreferenceProto>,
)

/** Preference getter API descriptor. */
class PreferenceGetterApiDescriptor(override val id: Int) :
    ApiDescriptor<PreferenceGetterRequest, PreferenceGetterResponse> {

    override val requestCodec = PreferenceGetterRequestCodec()

    override val responseCodec = PreferenceGetterResponseCodec()
}

/** Preference getter API implementation. */
class PreferenceGetterApiHandler(
    override val id: Int,
    private val permissionChecker: ApiPermissionChecker<PreferenceGetterRequest>,
) : ApiHandler<PreferenceGetterRequest, PreferenceGetterResponse> {

    override fun hasPermission(
        application: Application,
        myUid: Int,
        callingUid: Int,
        request: PreferenceGetterRequest,
    ) = permissionChecker.hasPermission(application, myUid, callingUid, request)

    override suspend fun invoke(
        application: Application,
        myUid: Int,
        callingUid: Int,
        request: PreferenceGetterRequest,
    ): PreferenceGetterResponse {
        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[screenKey]
            if (screenMetadata == null) {
                for (coordinate in coordinates) {
                    errors[coordinate] = PreferenceGetterErrorCode.NOT_FOUND
                }
                continue
            }
            val nodes = mutableMapOf<String, PreferenceHierarchyNode?>()
            for (coordinate in coordinates) nodes[coordinate.key] = null
            screenMetadata.getPreferenceHierarchy(application).forEachRecursively {
                val metadata = it.metadata
                val key = metadata.key
                if (nodes.containsKey(key)) nodes[key] = it
            }
            for (coordinate in coordinates) {
                val node = nodes[coordinate.key]
                if (node == null) {
                    errors[coordinate] = PreferenceGetterErrorCode.NOT_FOUND
                    continue
                }
                val metadata = node.metadata
                try {
                    val preferenceProto =
                        metadata.toProto(
                            application,
                            myUid,
                            callingUid,
                            screenMetadata,
                            metadata.key == screenMetadata.key,
                            flags,
                        )
                    if (flags == PreferenceGetterFlags.VALUE && !preferenceProto.hasValue()) {
                        errors[coordinate] = PreferenceGetterErrorCode.DISALLOW
                    } else {
                        preferences[coordinate] = preferenceProto
                    }
                } catch (e: Exception) {
                    errors[coordinate] = PreferenceGetterErrorCode.INTERNAL_ERROR
                }
            }
        }
        return PreferenceGetterResponse(errors, preferences)
    }

    override val requestCodec = PreferenceGetterRequestCodec()

    override val responseCodec = PreferenceGetterResponseCodec()
}
+126 −0
Original line number Original line 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.os.Bundle
import android.os.Parcel
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.ipc.MessageCodec
import java.util.Arrays

/** Message codec for [PreferenceGetterRequest]. */
class PreferenceGetterRequestCodec : MessageCodec<PreferenceGetterRequest> {
    override fun encode(data: PreferenceGetterRequest) =
        Bundle(2).apply {
            putParcelableArray(null, data.preferences)
            putInt(FLAGS, data.flags)
        }

    @Suppress("DEPRECATION")
    override fun decode(data: Bundle): PreferenceGetterRequest {
        data.classLoader = PreferenceCoordinate::class.java.classLoader
        val array = data.getParcelableArray(null)!!

        return PreferenceGetterRequest(
            Arrays.copyOf(array, array.size, Array<PreferenceCoordinate>::class.java),
            data.getInt(FLAGS),
        )
    }

    companion object {
        private const val FLAGS = "f"
    }
}

/** Message codec for [PreferenceGetterResponse]. */
class PreferenceGetterResponseCodec : MessageCodec<PreferenceGetterResponse> {
    override fun encode(data: PreferenceGetterResponse) =
        Bundle(2).apply {
            data.errors.toErrorsByteArray()?.let { putByteArray(ERRORS, it) }
            data.preferences.toPreferencesByteArray()?.let { putByteArray(null, it) }
        }

    private fun Map<PreferenceCoordinate, Int>.toErrorsByteArray(): ByteArray? {
        if (isEmpty()) return null
        val parcel = Parcel.obtain()
        parcel.writeInt(size)
        for ((coordinate, code) in this) {
            coordinate.writeToParcel(parcel, 0)
            parcel.writeInt(code)
        }
        val bytes = parcel.marshall()
        parcel.recycle()
        return bytes
    }

    private fun Map<PreferenceCoordinate, PreferenceProto>.toPreferencesByteArray(): ByteArray? {
        if (isEmpty()) return null
        val parcel = Parcel.obtain()
        parcel.writeInt(size)
        for ((coordinate, preferenceProto) in this) {
            coordinate.writeToParcel(parcel, 0)
            val data = preferenceProto.toByteArray()
            parcel.writeInt(data.size)
            parcel.writeByteArray(data)
        }
        val bytes = parcel.marshall()
        parcel.recycle()
        return bytes
    }

    override fun decode(data: Bundle) =
        PreferenceGetterResponse(
            data.getByteArray(ERRORS).toErrors(),
            data.getByteArray(null).toPreferences(),
        )

    private fun ByteArray?.toErrors(): Map<PreferenceCoordinate, Int> {
        if (this == null) return emptyMap()
        val parcel = Parcel.obtain()
        parcel.unmarshall(this, 0, size)
        parcel.setDataPosition(0)
        val count = parcel.readInt()
        val errors = mutableMapOf<PreferenceCoordinate, Int>()
        repeat(count) {
            val coordinate = PreferenceCoordinate(parcel)
            errors[coordinate] = parcel.readInt()
        }
        parcel.recycle()
        return errors
    }

    private fun ByteArray?.toPreferences(): Map<PreferenceCoordinate, PreferenceProto> {
        if (this == null) return emptyMap()
        val parcel = Parcel.obtain()
        parcel.unmarshall(this, 0, size)
        parcel.setDataPosition(0)
        val count = parcel.readInt()
        val preferences = mutableMapOf<PreferenceCoordinate, PreferenceProto>()
        repeat(count) {
            val coordinate = PreferenceCoordinate(parcel)
            val bytes = parcel.readInt()
            val array = ByteArray(bytes).also { parcel.readByteArray(it) }
            preferences[coordinate] = PreferenceProto.parseFrom(array)
        }
        parcel.recycle()
        return preferences
    }

    companion object {
        private const val ERRORS = "e"
    }
}
+6 −0
Original line number Original line Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.settingslib.service
package com.android.settingslib.service


import com.android.settingslib.graph.GetPreferenceGraphRequest
import com.android.settingslib.graph.GetPreferenceGraphRequest
import com.android.settingslib.graph.PreferenceGetterApiHandler
import com.android.settingslib.graph.PreferenceGetterRequest
import com.android.settingslib.graph.PreferenceSetterApiHandler
import com.android.settingslib.graph.PreferenceSetterApiHandler
import com.android.settingslib.graph.PreferenceSetterRequest
import com.android.settingslib.graph.PreferenceSetterRequest
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.ApiHandler
@@ -37,6 +39,7 @@ open class PreferenceService(
    preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> = setOf(),
    preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> = setOf(),
    graphPermissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>? = null,
    graphPermissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>? = null,
    setterPermissionChecker: ApiPermissionChecker<PreferenceSetterRequest>? = null,
    setterPermissionChecker: ApiPermissionChecker<PreferenceSetterRequest>? = null,
    getterPermissionChecker: ApiPermissionChecker<PreferenceGetterRequest>? = null,
    vararg apiHandlers: ApiHandler<*, *>,
    vararg apiHandlers: ApiHandler<*, *>,
) :
) :
    MessengerService(
    MessengerService(
@@ -45,6 +48,9 @@ open class PreferenceService(
            setterPermissionChecker?.let {
            setterPermissionChecker?.let {
                add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it))
                add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it))
            }
            }
            getterPermissionChecker?.let {
                add(PreferenceGetterApiHandler(API_PREFERENCE_GETTER, it))
            }
            addAll(apiHandlers)
            addAll(apiHandlers)
        },
        },
        permissionChecker,
        permissionChecker,
+3 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,9 @@ internal const val API_GET_PREFERENCE_GRAPH = 1
/** API id for preference value setter. */
/** API id for preference value setter. */
internal const val API_PREFERENCE_SETTER = 2
internal const val API_PREFERENCE_SETTER = 2


/** API id for preference getter. */
internal const val API_PREFERENCE_GETTER = 3

/**
/**
 * The max API id reserved for internal preference service usages. Custom API id should start with
 * The max API id reserved for internal preference service usages. Custom API id should start with
 * **1000** to avoid conflict.
 * **1000** to avoid conflict.