diff options
author | 2025-02-12 16:46:23 +0000 | |
---|---|---|
committer | 2025-03-04 16:13:32 -0800 | |
commit | 14c82ceecd06e0d395c7368beb611f8ce1bdfbf7 (patch) | |
tree | 0e15137238540fb321d4caf184121d32cc395c89 | |
parent | a440a7d4a085436aceb7becee8b53e2bb6e1626a (diff) |
Display: add System Settings Flow & Delegate
Added System Settings Flow that allows to observe and react on
Integer System settings values using Kotlin Coroutines. Also add
System Settings Integer delegate.
Flag: EXEMPT only usage guarded by another flag
Test: atest com.android.settingslib.spaprivileged.settingsprovider
Test: atest --iteration 30 -c frameworks/base/packages/SettingsLib/SpaPrivileged/tests/robotests
Test: verified that SettingsSystemIntegerFlow works with
`display_color_mode` Int setting w/ and /wo set value.
Bug: 390644464
Change-Id: Iacda050b9ff44bda4358756848b15319c699eef1
5 files changed, 296 insertions, 0 deletions
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt new file mode 100644 index 000000000000..db7a640be893 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 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.spaprivileged.settingsprovider + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings +import com.android.settingslib.spaprivileged.database.contentChangeFlow +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +fun Context.settingsSystemInteger( + name: String, + defaultValue: Int +): ReadWriteProperty<Any?, Int> = SettingsSystemIntegerDelegate(this, name, defaultValue) + +fun Context.settingsSystemIntegerFlow(name: String, defaultValue: Int): Flow<Int> { + val value by settingsSystemInteger(name, defaultValue) + return contentChangeFlow(Settings.System.getUriFor(name)) + .map { value } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.IO) +} + +private class SettingsSystemIntegerDelegate( + context: Context, + private val name: String, + private val defaultValue: Int, +) : ReadWriteProperty<Any?, Int> { + + private val contentResolver: ContentResolver = context.contentResolver + + override fun getValue(thisRef: Any?, property: KProperty<*>): Int = + Settings.System.getInt(contentResolver, name, defaultValue) + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { + Settings.System.putInt(contentResolver, name, value) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp new file mode 100644 index 000000000000..e3faf73a69fb --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp @@ -0,0 +1,59 @@ +// +// Copyright (C) 2025 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "SpaPrivilegedRoboTestStub", + defaults: [ + "SpaPrivilegedLib-defaults", + ], + platform_apis: true, + certificate: "platform", + privileged: true, +} + +android_robolectric_test { + name: "SpaPrivilegedRoboTests", + srcs: [ + ":SpaPrivilegedLib_srcs", + "src/**/*.java", + "src/**/*.kt", + ], + + defaults: [ + "SpaPrivilegedLib-defaults", + ], + + static_libs: [ + "SpaLibTestUtils", + "androidx.test.ext.junit", + "androidx.test.runner", + ], + + java_resource_dirs: [ + "config", + ], + + instrumentation_for: "SpaPrivilegedRoboTestStub", + + test_options: { + timeout: 36000, + }, + + strict_mode: false, +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml new file mode 100644 index 000000000000..113852d74f69 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + coreApp="true" + package="com.android.settingslib.spaprivileged.settingsprovider"> + + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + + <application/> +</manifest>
\ No newline at end of file diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties b/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties new file mode 100644 index 000000000000..95a24bde00f6 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties @@ -0,0 +1,16 @@ +/* +* Copyright (C) 2025 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. +*/ +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt new file mode 100644 index 000000000000..67e4180a624b --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2025 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.spaprivileged.settingsprovider + +import android.content.Context +import android.provider.Settings + +import androidx.test.core.app.ApplicationProvider + +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spa.testutils.toListWithTimeout +import com.google.common.truth.Truth.assertThat + +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SettingsSystemIntegerTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + @Before + fun setUp() { + Settings.System.putString(context.contentResolver, TEST_NAME, null) + } + + @Test + fun setIntValue_returnSameValueByDelegate() { + val settingValue = 250 + + Settings.System.putInt(context.contentResolver, TEST_NAME, settingValue) + + val value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + assertThat(value).isEqualTo(settingValue) + } + + @Test + fun setZero_returnZeroByDelegate() { + val settingValue = 0 + Settings.System.putInt(context.contentResolver, TEST_NAME, settingValue) + + val value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + assertThat(value).isEqualTo(settingValue) + } + + @Test + fun setValueByDelegate_getValueFromSettings() { + val settingsValue = 5 + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + value = settingsValue + + assertThat(Settings.System.getInt(context.contentResolver, TEST_NAME, TEST_SETTING_DEFAULT_VALUE)).isEqualTo(settingsValue) + } + + @Test + fun setZeroByDelegate_getZeroFromSettings() { + val settingValue = 0 + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + value = settingValue + + assertThat(Settings.System.getInt(context.contentResolver, TEST_NAME, TEST_SETTING_DEFAULT_VALUE)).isEqualTo(settingValue) + } + + @Test + fun setValueByDelegate_returnValueFromsettingsSystemIntegerFlow() = runBlocking<Unit> { + val settingValue = 7 + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = settingValue + + val flow = context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(settingValue) + } + + @Test + fun setValueByDelegateTwice_collectAfterValueChanged_onlyKeepLatest() = runBlocking<Unit> { + val firstSettingValue = 5 + val secondSettingValue = 10 + + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = firstSettingValue + + val flow = context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = secondSettingValue + + assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(value) + } + + @Test + fun settingsSystemIntegerFlow_collectBeforeValueChanged_getBoth() = runBlocking<Unit> { + val firstSettingValue = 12 + val secondSettingValue = 17 + val delay_ms = 100L + + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = firstSettingValue + + + val listDeferred = async { + context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE).toListWithTimeout() + } + + delay(delay_ms) + value = secondSettingValue + + assertThat(listDeferred.await()) + .containsAtLeast(firstSettingValue, secondSettingValue).inOrder() + } + + private companion object { + const val TEST_NAME = "test_system_integer_delegate" + const val TEST_SETTING_DEFAULT_VALUE = -1 + } +} |