Merge "Integrate Settings with Preference Service" into main

This commit is contained in:
Chris Antol
2024-12-06 02:22:11 +00:00
committed by Android (Google) Code Review
5 changed files with 495 additions and 0 deletions

View File

@@ -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",

View File

@@ -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>

View File

@@ -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))
}
}
}
}

View File

@@ -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()
}

View File

@@ -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)
}
}