diff options
7 files changed, 182 insertions, 18 deletions
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/database/ContentChangeFlow.kt index de5c558fea56..8f67354b76f3 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalFlow.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/database/ContentChangeFlow.kt @@ -14,12 +14,11 @@ * limitations under the License. */ -package com.android.settingslib.spaprivileged.settingsprovider +package com.android.settingslib.spaprivileged.database import android.content.Context import android.database.ContentObserver -import android.os.Handler -import android.provider.Settings +import android.net.Uri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -27,20 +26,19 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn -internal fun <T> Context.settingsGlobalFlow( - name: String, - sendInitialValue: Boolean = true, - value: () -> T, -): Flow<T> = callbackFlow { - val contentObserver = object : ContentObserver(Handler.getMain()) { +/** Content change flow for the given [uri]. */ +internal fun Context.contentChangeFlow( + uri: Uri, + sendInitial: Boolean = true, +): Flow<Unit> = callbackFlow { + val contentObserver = object : ContentObserver(null) { override fun onChange(selfChange: Boolean) { - trySend(value()) + trySend(Unit) } } - val uri = Settings.Global.getUriFor(name) contentResolver.registerContentObserver(uri, false, contentObserver) - if (sendInitialValue) { - trySend(value()) + if (sendInitial) { + trySend(Unit) } awaitClose { contentResolver.unregisterContentObserver(contentObserver) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBoolean.kt index 8e28bf8e632d..5d67f57136e3 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBoolean.kt @@ -22,13 +22,15 @@ import android.provider.Settings import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map fun Context.settingsGlobalBoolean(name: String, defaultValue: Boolean = false): ReadWriteProperty<Any?, Boolean> = SettingsGlobalBooleanDelegate(this, name, defaultValue) fun Context.settingsGlobalBooleanFlow(name: String, defaultValue: Boolean = false): Flow<Boolean> { val value by settingsGlobalBoolean(name, defaultValue) - return settingsGlobalFlow(name) { value } + return settingsGlobalChangeFlow(name).map { value }.distinctUntilChanged() } private class SettingsGlobalBooleanDelegate( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlow.kt index 4098987256a2..675b2e5db430 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlow.kt @@ -17,7 +17,9 @@ package com.android.settingslib.spaprivileged.settingsprovider import android.content.Context +import android.provider.Settings +import com.android.settingslib.spaprivileged.database.contentChangeFlow import kotlinx.coroutines.flow.Flow fun Context.settingsGlobalChangeFlow(name: String, sendInitialValue: Boolean = true): Flow<Unit> = - settingsGlobalFlow(name, sendInitialValue) { } + contentChangeFlow(Settings.Global.getUriFor(name), sendInitialValue) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt new file mode 100644 index 000000000000..25090a4140a4 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +fun Context.settingsSecureBoolean(name: String, defaultValue: Boolean = false): + ReadWriteProperty<Any?, Boolean> = SettingsSecureBooleanDelegate(this, name, defaultValue) + +fun Context.settingsSecureBooleanFlow(name: String, defaultValue: Boolean = false): Flow<Boolean> { + val value by settingsSecureBoolean(name, defaultValue) + return contentChangeFlow(Settings.Secure.getUriFor(name)).map { value }.distinctUntilChanged() +} + +private class SettingsSecureBooleanDelegate( + context: Context, + private val name: String, + private val defaultValue: Boolean = false, +) : ReadWriteProperty<Any?, Boolean> { + + private val contentResolver: ContentResolver = context.contentResolver + + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean = + Settings.Secure.getInt(contentResolver, name, if (defaultValue) 1 else 0) != 0 + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { + Settings.Secure.putInt(contentResolver, name, if (value) 1 else 0) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt index 996c2d57c38a..70b38feae9d5 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt @@ -30,7 +30,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class SettingsGlobalBooleanRepositoryTest { +class SettingsGlobalBooleanTest { private val context: Context = ApplicationProvider.getApplicationContext() diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt index 37e00e6f671b..2e6a39603deb 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt @@ -29,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class SettingsGlobalChangeRepositoryTest { +class SettingsGlobalChangeFlowTest { private val context: Context = ApplicationProvider.getApplicationContext() @@ -52,7 +52,7 @@ class SettingsGlobalChangeRepositoryTest { var value by context.settingsGlobalBoolean(TEST_NAME) value = false - val flow = context.settingsGlobalBooleanFlow(TEST_NAME) + val flow = context.settingsGlobalChangeFlow(TEST_NAME) value = true assertThat(flow.toListWithTimeout()).hasSize(1) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt new file mode 100644 index 000000000000..29a89be87acd --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 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 androidx.test.ext.junit.runners.AndroidJUnit4 +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.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsSecureBooleanTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun getValue_setTrue_returnTrue() { + Settings.Secure.putInt(context.contentResolver, TEST_NAME, 1) + + val value by context.settingsSecureBoolean(TEST_NAME) + + assertThat(value).isTrue() + } + + @Test + fun getValue_setFalse_returnFalse() { + Settings.Secure.putInt(context.contentResolver, TEST_NAME, 0) + + val value by context.settingsSecureBoolean(TEST_NAME) + + assertThat(value).isFalse() + } + + @Test + fun setValue_setTrue_returnTrue() { + var value by context.settingsSecureBoolean(TEST_NAME) + + value = true + + assertThat(Settings.Secure.getInt(context.contentResolver, TEST_NAME, 0)).isEqualTo(1) + } + + @Test + fun setValue_setFalse_returnFalse() { + var value by context.settingsSecureBoolean(TEST_NAME) + + value = false + + assertThat(Settings.Secure.getInt(context.contentResolver, TEST_NAME, 1)).isEqualTo(0) + } + + @Test + fun settingsSecureBooleanFlow_valueNotChanged() = runBlocking { + var value by context.settingsSecureBoolean(TEST_NAME) + value = false + + val flow = context.settingsSecureBooleanFlow(TEST_NAME) + + assertThat(flow.firstWithTimeoutOrNull()).isFalse() + } + + @Test + fun settingsSecureBooleanFlow_collectAfterValueChanged_onlyKeepLatest() = runBlocking { + var value by context.settingsSecureBoolean(TEST_NAME) + value = false + + val flow = context.settingsSecureBooleanFlow(TEST_NAME) + value = true + + assertThat(flow.firstWithTimeoutOrNull()).isTrue() + } + + @Test + fun settingsSecureBooleanFlow_collectBeforeValueChanged_getBoth() = runBlocking { + var value by context.settingsSecureBoolean(TEST_NAME) + value = false + + val listDeferred = async { + context.settingsSecureBooleanFlow(TEST_NAME).toListWithTimeout() + } + delay(100) + value = true + + assertThat(listDeferred.await()).containsExactly(false, true).inOrder() + } + + private companion object { + const val TEST_NAME = "test_boolean_delegate" + } +} |