Revert^2 "Integrate Settings with Preference Service"
This reverts commit b2986f77a4fff560fba23492963645d32bf30bb8.
Reason for revert: Re-introduce change, fixing bad test
Change-Id: Ie4e19b4691b335bab69364d12afb832e8c9b0ce1
diff --git a/Android.bp b/Android.bp
index a160785..907ff12 100644
--- a/Android.bp
+++ b/Android.bp
@@ -132,6 +132,7 @@
],
flags_packages: [
"aconfig_settings_flags",
+ "aconfig_settingslib_flags",
"android.app.flags-aconfig",
"android.provider.flags-aconfig",
"android.security.flags-aconfig",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index db2e0d4..2295ee3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5412,6 +5412,17 @@
</intent-filter>
</service>
+ <!-- Service to expose Preference Metadata and Get/Set functionality -->
+ <service
+ android:name=".service.PreferenceService"
+ android:exported="true"
+ android:featureFlag="com.android.settingslib.flags.settings_catalyst"
+ android:permission="android.permission.READ_SYSTEM_PREFERENCES">
+ <intent-filter>
+ <action android:name="android.service.settings.preferences.action.PREFERENCE_SERVICE" />
+ </intent-filter>
+ </service>
+
<receiver android:name="com.android.settings.connecteddevice.audiosharing.AudioSharingReceiver"
android:exported="false">
<intent-filter>
diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt
new file mode 100644
index 0000000..3a67762
--- /dev/null
+++ b/src/com/android/settings/service/PreferenceService.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.settings.service
+
+import android.os.Binder
+import android.os.OutcomeReceiver
+import android.os.Process
+import android.service.settings.preferences.GetValueRequest
+import android.service.settings.preferences.GetValueResult
+import android.service.settings.preferences.MetadataRequest
+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.PreferenceGetterApiHandler
+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.launch
+import java.lang.Exception
+
+class PreferenceService : SettingsPreferenceService() {
+
+ private val scope = CoroutineScope(Job() + Dispatchers.Main)
+
+ private val getApiHandler = PreferenceGetterApiHandler(1, ApiPermissionChecker.alwaysAllow())
+ private val setApiHandler = PreferenceSetterApiHandler(2, ApiPermissionChecker.alwaysAllow())
+
+ 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"))
+ }
+
+ override fun onGetPreferenceValue(
+ request: GetValueRequest,
+ callback: OutcomeReceiver<GetValueResult, Exception>
+ ) {
+ scope.launch(Dispatchers.IO) {
+ val apiRequest = transformFrameworkGetValueRequest(request)
+ val response = getApiHandler.invoke(application, Process.myUid(),
+ Binder.getCallingPid(), apiRequest)
+ val result = transformCatalystGetValueResponse(
+ this@PreferenceService,
+ request,
+ response
+ )
+ if (result == null) {
+ callback.onError(IllegalStateException("No response"))
+ } else {
+ callback.onResult(result)
+ }
+ }
+ }
+
+ override fun onSetPreferenceValue(
+ request: SetValueRequest,
+ callback: OutcomeReceiver<SetValueResult, Exception>
+ ) {
+ scope.launch(Dispatchers.IO) {
+ val apiRequest = transformFrameworkSetValueRequest(request)
+ if (apiRequest == null) {
+ callback.onResult(
+ SetValueResult.Builder(SetValueResult.RESULT_INVALID_REQUEST).build()
+ )
+ } else {
+ val response = setApiHandler.invoke(application, Process.myUid(),
+ Binder.getCallingPid(), apiRequest)
+
+ callback.onResult(transformCatalystSetValueResponse(response))
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
new file mode 100644
index 0000000..7a4c7fc
--- /dev/null
+++ b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.settings.service
+
+import android.content.Context
+import android.service.settings.preferences.GetValueRequest
+import android.service.settings.preferences.GetValueResult
+import android.service.settings.preferences.SetValueRequest
+import android.service.settings.preferences.SetValueResult
+import android.service.settings.preferences.SettingsPreferenceMetadata
+import android.service.settings.preferences.SettingsPreferenceValue
+import com.android.settingslib.graph.PreferenceCoordinate
+import com.android.settingslib.graph.PreferenceGetterErrorCode
+import com.android.settingslib.graph.PreferenceGetterFlags
+import com.android.settingslib.graph.PreferenceGetterRequest
+import com.android.settingslib.graph.PreferenceGetterResponse
+import com.android.settingslib.graph.PreferenceSetterRequest
+import com.android.settingslib.graph.PreferenceSetterResult
+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.toIntent
+import com.android.settingslib.metadata.SensitivityLevel
+
+/** Translate Framework GET VALUE request to Catalyst GET VALUE request */
+fun transformFrameworkGetValueRequest(
+ request: GetValueRequest,
+ flags: Int = PreferenceGetterFlags.ALL
+): PreferenceGetterRequest {
+ val coord = PreferenceCoordinate(request.screenKey, request.preferenceKey)
+ return PreferenceGetterRequest(
+ arrayOf(coord),
+ flags
+ )
+}
+
+/** Translate Catalyst GET VALUE result to Framework GET VALUE result */
+fun transformCatalystGetValueResponse(
+ context: Context,
+ request: GetValueRequest,
+ response: PreferenceGetterResponse
+): GetValueResult? {
+ val coord = PreferenceCoordinate(request.screenKey, request.preferenceKey)
+ val errorResponse = response.errors[coord]
+ val valueResponse = response.preferences[coord]
+ when {
+ errorResponse != null -> {
+ val errorCode = when (errorResponse) {
+ PreferenceGetterErrorCode.NOT_FOUND -> GetValueResult.RESULT_UNSUPPORTED
+ PreferenceGetterErrorCode.DISALLOW -> GetValueResult.RESULT_DISALLOW
+ else -> GetValueResult.RESULT_INTERNAL_ERROR
+ }
+ return GetValueResult.Builder(errorCode).build()
+ }
+ valueResponse != null -> {
+ val resultBuilder = GetValueResult.Builder(GetValueResult.RESULT_OK)
+ resultBuilder.setMetadata(valueResponse.toMetadata(context, coord.screenKey))
+ val prefValue = valueResponse.value
+ when (prefValue.valueCase.number) {
+ PreferenceValueProto.BOOLEAN_VALUE_FIELD_NUMBER -> {
+ resultBuilder.setValue(
+ SettingsPreferenceValue.Builder(
+ SettingsPreferenceValue.TYPE_BOOLEAN
+ ).setBooleanValue(prefValue.booleanValue)
+ .build()
+ )
+ return resultBuilder.build()
+ }
+ PreferenceValueProto.INT_VALUE_FIELD_NUMBER -> {
+ resultBuilder.setValue(
+ SettingsPreferenceValue.Builder(
+ SettingsPreferenceValue.TYPE_INT
+ ).setIntValue(prefValue.intValue)
+ .build()
+ )
+ return resultBuilder.build()
+ }
+ }
+ return GetValueResult.Builder(
+ GetValueResult.RESULT_UNSUPPORTED
+ ).build()
+ }
+ else -> return null
+ }
+}
+
+/** Translate Framework SET VALUE request to Catalyst SET VALUE request */
+fun transformFrameworkSetValueRequest(request: SetValueRequest): PreferenceSetterRequest? {
+ val valueProto = when (request.preferenceValue.type) {
+ SettingsPreferenceValue.TYPE_BOOLEAN -> preferenceValueProto {
+ booleanValue = request.preferenceValue.booleanValue
+ }
+ SettingsPreferenceValue.TYPE_INT -> preferenceValueProto {
+ intValue = request.preferenceValue.intValue
+ }
+ else -> null
+ }
+ return valueProto?.let {
+ PreferenceSetterRequest(request.screenKey, request.preferenceKey, it)
+ }
+}
+
+/** Translate Catalyst SET VALUE result to Framework SET VALUE result */
+fun transformCatalystSetValueResponse(@PreferenceSetterResult response: Int): SetValueResult {
+ val resultCode = when (response) {
+ PreferenceSetterResult.OK -> SetValueResult.RESULT_OK
+ PreferenceSetterResult.UNAVAILABLE -> SetValueResult.RESULT_UNAVAILABLE
+ PreferenceSetterResult.DISABLED -> SetValueResult.RESULT_DISABLED
+ PreferenceSetterResult.UNSUPPORTED -> SetValueResult.RESULT_UNSUPPORTED
+ PreferenceSetterResult.DISALLOW -> SetValueResult.RESULT_DISALLOW
+ PreferenceSetterResult.REQUIRE_APP_PERMISSION ->
+ SetValueResult.RESULT_REQUIRE_APP_PERMISSION
+ PreferenceSetterResult.REQUIRE_USER_AGREEMENT -> SetValueResult.RESULT_REQUIRE_USER_CONSENT
+ PreferenceSetterResult.RESTRICTED -> SetValueResult.RESULT_RESTRICTED
+ PreferenceSetterResult.INVALID_REQUEST -> SetValueResult.RESULT_INVALID_REQUEST
+ else -> SetValueResult.RESULT_INTERNAL_ERROR
+ }
+ return SetValueResult.Builder(resultCode).build()
+}
+
+private fun PreferenceProto.toMetadata(
+ context: Context,
+ screenKey: String
+): SettingsPreferenceMetadata {
+ val sensitivity = when (sensitivityLevel) {
+ SensitivityLevel.NO_SENSITIVITY -> SettingsPreferenceMetadata.NO_SENSITIVITY
+ SensitivityLevel.LOW_SENSITIVITY -> SettingsPreferenceMetadata.EXPECT_POST_CONFIRMATION
+ SensitivityLevel.MEDIUM_SENSITIVITY -> SettingsPreferenceMetadata.EXPECT_PRE_CONFIRMATION
+ else -> SettingsPreferenceMetadata.NO_DIRECT_ACCESS
+ }
+ return SettingsPreferenceMetadata.Builder(screenKey, key)
+ .setTitle(title.getText(context))
+ .setSummary(summary.getText(context))
+ .setEnabled(enabled)
+ .setAvailable(available)
+ .setRestricted(restricted)
+ .setWritable(persistent)
+ .setLaunchIntent(launchIntent.toIntent())
+ .setWriteSensitivity(sensitivity)
+ .build()
+}
diff --git a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt
new file mode 100644
index 0000000..f064b22
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.settings.service
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.platform.test.annotations.RequiresFlagsEnabled
+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.SetValueRequest
+import android.service.settings.preferences.SetValueResult
+import android.service.settings.preferences.SettingsPreferenceMetadata
+import android.service.settings.preferences.SettingsPreferenceValue
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.homepage.SettingsHomepageActivity
+import com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST
+import com.android.settingslib.graph.PreferenceCoordinate
+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.proto.PreferenceProto
+import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.TextProto
+import com.android.settingslib.graph.toProto
+import com.android.settingslib.metadata.SensitivityLevel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@RequiresFlagsEnabled(FLAG_SETTINGS_CATALYST)
+class PreferenceServiceRequestTransformerTest {
+
+ @get:Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Test
+ fun transformFrameworkGetValueRequest_returnsValidCatalystRequest() {
+ val fRequest = GetValueRequest.Builder("screen", "pref").build()
+ val cRequest = transformFrameworkGetValueRequest(fRequest)
+ with(cRequest) {
+ assertThat(preferences).hasLength(1)
+ assertThat(preferences.first().screenKey).isEqualTo(fRequest.screenKey)
+ assertThat(preferences.first().key).isEqualTo(fRequest.preferenceKey)
+ assertThat(flags).isEqualTo(PreferenceGetterFlags.ALL)
+ }
+ }
+
+ @Test
+ fun transformCatalystGetValueResponse_success_returnsValidFrameworkResponse() {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ val fRequest = GetValueRequest.Builder("screen", "key").build()
+ val cResult = PreferenceGetterResponse(
+ emptyMap(),
+ mapOf(
+ PreferenceCoordinate(fRequest.screenKey, fRequest.preferenceKey) to
+ PreferenceProto.newBuilder()
+ .setKey("key")
+ .setTitle(TextProto.newBuilder().setString("title"))
+ .setSummary(TextProto.newBuilder().setString("summary"))
+ .setEnabled(true)
+ .setAvailable(true)
+ .setRestricted(true)
+ .setPersistent(true)
+ .setSensitivityLevel(SensitivityLevel.LOW_SENSITIVITY)
+ .setLaunchIntent(
+ Intent(context, SettingsHomepageActivity::class.java).toProto()
+ )
+ .setValue(PreferenceValueProto.newBuilder().setBooleanValue(true))
+ .build()
+ )
+ )
+ val fResult = transformCatalystGetValueResponse(context, fRequest, cResult)
+ assertThat(fResult!!.resultCode).isEqualTo(GetValueResult.RESULT_OK)
+ with(fResult.metadata!!) {
+ assertThat(title).isEqualTo("title")
+ assertThat(summary).isEqualTo("summary")
+ assertThat(isEnabled).isTrue()
+ assertThat(isAvailable).isTrue()
+ assertThat(isRestricted).isTrue()
+ assertThat(isWritable).isTrue()
+ assertThat(writeSensitivity)
+ .isEqualTo(SettingsPreferenceMetadata.EXPECT_POST_CONFIRMATION)
+ assertThat(launchIntent).isNotNull()
+ assertThat(launchIntent!!.component!!.className)
+ .isEqualTo(SettingsHomepageActivity::class.java.name)
+ }
+ with(fResult.value!!) {
+ assertThat(type).isEqualTo(SettingsPreferenceValue.TYPE_BOOLEAN)
+ assertThat(booleanValue).isTrue()
+ }
+ }
+
+ @Test
+ fun transformCatalystGetValueResponse_failure_returnsValidFrameworkResponse() {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ val fRequest = GetValueRequest.Builder("screen", "key").build()
+ val cResult = PreferenceGetterResponse(
+ mapOf(
+ PreferenceCoordinate(fRequest.screenKey, fRequest.preferenceKey) to
+ PreferenceGetterErrorCode.NOT_FOUND
+ ),
+ emptyMap()
+ )
+ val fResult = transformCatalystGetValueResponse(context, fRequest, cResult)
+ with(fResult!!) {
+ assertThat(resultCode).isEqualTo(GetValueResult.RESULT_UNSUPPORTED)
+ assertThat(metadata).isNull()
+ assertThat(value).isNull()
+ }
+ }
+
+ @Test
+ fun transformCatalystGetValueResponse_invalidResponse_returnsNull() {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ val fRequest = GetValueRequest.Builder("screen", "key").build()
+ val cResult = PreferenceGetterResponse(emptyMap(), emptyMap())
+ val fResult = transformCatalystGetValueResponse(context, fRequest, cResult)
+ assertThat(fResult).isNull()
+ }
+
+ @Test
+ fun transformFrameworkSetValueRequest_typeBoolean_returnsValidCatalystRequest() {
+ val fRequest = SetValueRequest.Builder(
+ "screen",
+ "pref",
+ SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_BOOLEAN)
+ .setBooleanValue(true)
+ .build()
+ ).build()
+ val cRequest = transformFrameworkSetValueRequest(fRequest)
+ with(cRequest!!) {
+ assertThat(screenKey).isEqualTo(fRequest.screenKey)
+ assertThat(key).isEqualTo(fRequest.preferenceKey)
+ assertThat(value.hasBooleanValue()).isTrue()
+ assertThat(value.booleanValue).isTrue()
+ }
+ }
+
+ @Test
+ fun transformFrameworkSetValueRequest_typeInt_returnsValidCatalystRequest() {
+ val fRequest = SetValueRequest.Builder(
+ "screen",
+ "pref",
+ SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_INT)
+ .setIntValue(5)
+ .build()
+ ).build()
+ val cRequest = transformFrameworkSetValueRequest(fRequest)
+ with(cRequest!!) {
+ assertThat(screenKey).isEqualTo(fRequest.screenKey)
+ assertThat(key).isEqualTo(fRequest.preferenceKey)
+ assertThat(value.hasIntValue()).isTrue()
+ assertThat(value.intValue).isEqualTo(5)
+ }
+ }
+
+ @Test
+ fun transformFrameworkSetValueRequest_typeString_returnsNull() {
+ val fRequest = SetValueRequest.Builder(
+ "screen",
+ "pref",
+ SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_STRING)
+ .setStringValue("value")
+ .build()
+ ).build()
+ val cRequest = transformFrameworkSetValueRequest(fRequest)
+ assertThat(cRequest).isNull()
+ }
+
+ @Test
+ fun transformCatalystSetValueResponse_returnsValidFrameworkResponse() {
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.OK).resultCode
+ ).isEqualTo(SetValueResult.RESULT_OK)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.UNAVAILABLE).resultCode
+ ).isEqualTo(SetValueResult.RESULT_UNAVAILABLE)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.DISABLED).resultCode
+ ).isEqualTo(SetValueResult.RESULT_DISABLED)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.UNSUPPORTED).resultCode
+ ).isEqualTo(SetValueResult.RESULT_UNSUPPORTED)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.DISALLOW).resultCode
+ ).isEqualTo(SetValueResult.RESULT_DISALLOW)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.REQUIRE_APP_PERMISSION)
+ .resultCode
+ ).isEqualTo(SetValueResult.RESULT_REQUIRE_APP_PERMISSION)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.REQUIRE_USER_AGREEMENT)
+ .resultCode
+ ).isEqualTo(SetValueResult.RESULT_REQUIRE_USER_CONSENT)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.RESTRICTED).resultCode
+ ).isEqualTo(SetValueResult.RESULT_RESTRICTED)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.INVALID_REQUEST).resultCode
+ ).isEqualTo(SetValueResult.RESULT_INVALID_REQUEST)
+
+ assertThat(
+ transformCatalystSetValueResponse(PreferenceSetterResult.INTERNAL_ERROR).resultCode
+ ).isEqualTo(SetValueResult.RESULT_INTERNAL_ERROR)
+ }
+}
\ No newline at end of file