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