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

Commit 5e0ea6fd authored by Jacky Wang's avatar Jacky Wang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "catalyst" into main

* changes:
  [Catalyst] Provide preference Getter API
  [Catalyst] Support flags to get preference graph
parents f98608d8 26474cbd
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ abstract class GetPreferenceGraphApiHandler(
        callingUid: Int,
        request: GetPreferenceGraphRequest,
    ): PreferenceGraphProto {
        val builder = PreferenceGraphBuilder.of(application, request)
        val builder = PreferenceGraphBuilder.of(application, myUid, callingUid, request)
        if (request.screenKeys.isEmpty()) {
            for (key in PreferenceScreenRegistry.preferenceScreens.keys) {
                builder.addPreferenceScreenFromRegistry(key)
@@ -68,16 +68,18 @@ constructor(
    val screenKeys: Set<String> = setOf(),
    val visitedScreens: Set<String> = setOf(),
    val locale: Locale? = null,
    val includeValue: Boolean = true,
    val flags: Int = PreferenceGetterFlags.ALL,
    val includeValue: Boolean = true, // TODO: clean up
    val includeValueDescriptor: Boolean = true,
)

object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
    override fun encode(data: GetPreferenceGraphRequest): Bundle =
        Bundle(3).apply {
        Bundle(4).apply {
            putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray())
            putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray())
            putString(KEY_LOCALE, data.locale?.toLanguageTag())
            putInt(KEY_FLAGS, data.flags)
        }

    override fun decode(data: Bundle): GetPreferenceGraphRequest {
@@ -88,12 +90,14 @@ object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest>
            screenKeys.toSet(),
            visitedScreens.toSet(),
            data.getString(KEY_LOCALE).toLocale(),
            data.getInt(KEY_FLAGS),
        )
    }

    private const val KEY_SCREEN_KEYS = "k"
    private const val KEY_VISITED_KEYS = "v"
    private const val KEY_LOCALE = "l"
    private const val KEY_FLAGS = "f"
}

object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> {
+45 −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.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 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 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"
    }
}
+31 −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

/** Flags for preference getter operation. */
object PreferenceGetterFlags {
    const val VALUE = 1 shl 0
    const val VALUE_DESCRIPTOR = 1 shl 1
    const val METADATA = 1 shl 2
    const val ALL = (1 shl 3) - 1

    fun Int.includeValue() = (this and VALUE) != 0

    fun Int.includeValueDescriptor() = (this and VALUE_DESCRIPTOR) != 0

    fun Int.includeMetadata() = (this and METADATA) != 0
}
Loading