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

Commit d41b7cb3 authored by Chris Antol's avatar Chris Antol Committed by Android (Google) Code Review
Browse files

Merge "Revert^2 "Integrate Settings with Preference Service"" into main

parents 42ca1c6a 7935068c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -132,6 +132,7 @@ android_library {
    ],
    flags_packages: [
        "aconfig_settings_flags",
        "aconfig_settingslib_flags",
        "android.app.flags-aconfig",
        "android.provider.flags-aconfig",
        "android.security.flags-aconfig",
+11 −0
Original line number Diff line number Diff line
@@ -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>
+92 −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.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))
            }
        }
    }
}
+156 −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.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()
}
+235 −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.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