diff options
5 files changed, 328 insertions, 0 deletions
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt new file mode 100644 index 000000000000..68aa2d258295 --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt @@ -0,0 +1,45 @@ +/* + * 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) + } +} diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt new file mode 100644 index 000000000000..c8453efb9161 --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt @@ -0,0 +1,148 @@ +/* + * 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() +} diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt new file mode 100644 index 000000000000..ff14eb5aae55 --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt @@ -0,0 +1,126 @@ +/* + * 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" + } +} diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt index ed748bb58a9a..7cb36db856eb 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt @@ -17,6 +17,8 @@ package com.android.settingslib.service 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.PreferenceSetterRequest import com.android.settingslib.ipc.ApiHandler @@ -37,6 +39,7 @@ open class PreferenceService( preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> = setOf(), graphPermissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>? = null, setterPermissionChecker: ApiPermissionChecker<PreferenceSetterRequest>? = null, + getterPermissionChecker: ApiPermissionChecker<PreferenceGetterRequest>? = null, vararg apiHandlers: ApiHandler<*, *>, ) : MessengerService( @@ -45,6 +48,9 @@ open class PreferenceService( setterPermissionChecker?.let { add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it)) } + getterPermissionChecker?.let { + add(PreferenceGetterApiHandler(API_PREFERENCE_GETTER, it)) + } addAll(apiHandlers) }, permissionChecker, diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt index 7655daa6cf21..d71405e126ce 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt @@ -24,6 +24,9 @@ internal const val API_GET_PREFERENCE_GRAPH = 1 /** API id for preference value setter. */ 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 * **1000** to avoid conflict. |