summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt45
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt148
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt126
-rw-r--r--packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt6
-rw-r--r--packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt3
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.