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

Commit 2c555e9b authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Create settingsSecureStringFlow

To access Settings.Secure strings easily.

Bug: 320076351
Test: unit test
Change-Id: I5fd54ce18f8c6fc71734eeeaab303e6697fbea47
parent 43087c04
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -21,16 +21,25 @@ import android.content.Context
import android.provider.Settings
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.settingsGlobalBoolean(name: String, defaultValue: Boolean = false):
    ReadWriteProperty<Any?, Boolean> = SettingsGlobalBooleanDelegate(this, name, defaultValue)
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 settingsGlobalChangeFlow(name).map { value }.distinctUntilChanged()
    return settingsGlobalChangeFlow(name)
        .map { value }
        .distinctUntilChanged()
        .conflate()
        .flowOn(Dispatchers.Default)
}

private class SettingsGlobalBooleanDelegate(
+12 −3
Original line number Diff line number Diff line
@@ -22,16 +22,25 @@ 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.settingsSecureBoolean(name: String, defaultValue: Boolean = false):
    ReadWriteProperty<Any?, Boolean> = SettingsSecureBooleanDelegate(this, name, defaultValue)
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()
    return contentChangeFlow(Settings.Secure.getUriFor(name))
        .map { value }
        .distinctUntilChanged()
        .conflate()
        .flowOn(Dispatchers.Default)
}

private class SettingsSecureBooleanDelegate(
+60 −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.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.settingsSecureString(
    name: String,
    defaultValue: String = ""
): ReadWriteProperty<Any?, String> = SettingsSecureStringDelegate(this, name, defaultValue)

fun Context.settingsSecureStringFlow(name: String, defaultValue: String = ""): Flow<String> {
    val value by settingsSecureString(name, defaultValue)
    return contentChangeFlow(Settings.Secure.getUriFor(name))
        .map { value }
        .distinctUntilChanged()
        .conflate()
        .flowOn(Dispatchers.Default)
}

private class SettingsSecureStringDelegate(
    context: Context,
    private val name: String,
    private val defaultValue: String = "",
) : ReadWriteProperty<Any?, String> {

    private val contentResolver: ContentResolver = context.contentResolver

    override fun getValue(thisRef: Any?, property: KProperty<*>): String =
        Settings.Secure.getString(contentResolver, name) ?: defaultValue

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        Settings.Secure.putString(contentResolver, name, value)
    }
}
+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.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 SettingsSecureStringTest {

    private val context: Context = ApplicationProvider.getApplicationContext()

    @Test
    fun getValue_returnCorrectValue() {
        Settings.Secure.putString(context.contentResolver, TEST_NAME, VALUE)

        val value by context.settingsSecureString(TEST_NAME)

        assertThat(value).isEqualTo(VALUE)
    }

    @Test
    fun setValue_correctValueSet() {
        var value by context.settingsSecureString(TEST_NAME)

        value = VALUE

        assertThat(Settings.Secure.getString(context.contentResolver, TEST_NAME)).isEqualTo(VALUE)
    }

    @Test
    fun settingsSecureStringFlow_valueNotChanged() = runBlocking {
        var value by context.settingsSecureString(TEST_NAME)
        value = VALUE

        val flow = context.settingsSecureStringFlow(TEST_NAME)

        assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(VALUE)
    }

    @Test
    fun settingsSecureStringFlow_collectAfterValueChanged_onlyKeepLatest() = runBlocking {
        var value by context.settingsSecureString(TEST_NAME)
        value = ""

        val flow = context.settingsSecureStringFlow(TEST_NAME)
        value = VALUE

        assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(VALUE)
    }

    @Test
    fun settingsSecureStringFlow_collectBeforeValueChanged_getBoth() = runBlocking {
        var value by context.settingsSecureString(TEST_NAME)
        value = ""

        val listDeferred = async { context.settingsSecureStringFlow(TEST_NAME).toListWithTimeout() }
        delay(100)
        value = VALUE

        assertThat(listDeferred.await()).containsAtLeast("", VALUE).inOrder()
    }

    private companion object {
        const val TEST_NAME = "test_string_delegate"
        const val VALUE = "value"
    }
}