Merge "Add OverridableFlow for Spa"
This commit is contained in:
@@ -54,11 +54,6 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -51,11 +51,6 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2022 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.spa.framework.compose
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* *************************************************************************************************
|
||||
* This file was forked from AndroidX:
|
||||
* lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
|
||||
* TODO: Replace with AndroidX when it's usable.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collects values from this [StateFlow] and represents its latest value via [State] in a
|
||||
* lifecycle-aware manner.
|
||||
*
|
||||
* The [StateFlow.value] is used as an initial value. Every time there would be new value posted
|
||||
* into the [StateFlow] the returned [State] will be updated causing recomposition of every
|
||||
* [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
|
||||
*
|
||||
* This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
|
||||
* [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
|
||||
* falls below [minActiveState].
|
||||
*
|
||||
* @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
|
||||
*
|
||||
* Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
|
||||
* parameter will throw an [IllegalArgumentException].
|
||||
*
|
||||
* @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
|
||||
* flow.
|
||||
* @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
|
||||
* collection will stop if the lifecycle falls below that state, and will restart if it's in that
|
||||
* state again.
|
||||
* @param context [CoroutineContext] to use for collecting.
|
||||
*/
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Composable
|
||||
fun <T> StateFlow<T>.collectAsStateWithLifecycle(
|
||||
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
|
||||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
): State<T> = collectAsStateWithLifecycle(
|
||||
initialValue = this.value,
|
||||
lifecycle = lifecycleOwner.lifecycle,
|
||||
minActiveState = minActiveState,
|
||||
context = context
|
||||
)
|
||||
|
||||
/**
|
||||
* Collects values from this [StateFlow] and represents its latest value via [State] in a
|
||||
* lifecycle-aware manner.
|
||||
*
|
||||
* The [StateFlow.value] is used as an initial value. Every time there would be new value posted
|
||||
* into the [StateFlow] the returned [State] will be updated causing recomposition of every
|
||||
* [State.value] usage whenever the [lifecycle] is at least [minActiveState].
|
||||
*
|
||||
* This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
|
||||
* state. The collection stops when [lifecycle] falls below [minActiveState].
|
||||
*
|
||||
* @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
|
||||
*
|
||||
* Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
|
||||
* parameter will throw an [IllegalArgumentException].
|
||||
*
|
||||
* @param lifecycle [Lifecycle] used to restart collecting `this` flow.
|
||||
* @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
|
||||
* collection will stop if the lifecycle falls below that state, and will restart if it's in that
|
||||
* state again.
|
||||
* @param context [CoroutineContext] to use for collecting.
|
||||
*/
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Composable
|
||||
fun <T> StateFlow<T>.collectAsStateWithLifecycle(
|
||||
lifecycle: Lifecycle,
|
||||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
): State<T> = collectAsStateWithLifecycle(
|
||||
initialValue = this.value,
|
||||
lifecycle = lifecycle,
|
||||
minActiveState = minActiveState,
|
||||
context = context
|
||||
)
|
||||
|
||||
/**
|
||||
* Collects values from this [Flow] and represents its latest value via [State] in a
|
||||
* lifecycle-aware manner.
|
||||
*
|
||||
* Every time there would be new value posted into the [Flow] the returned [State] will be updated
|
||||
* causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
|
||||
* at least [minActiveState].
|
||||
*
|
||||
* This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
|
||||
* Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
|
||||
* [minActiveState].
|
||||
*
|
||||
* @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
|
||||
*
|
||||
* Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
|
||||
* parameter will throw an [IllegalArgumentException].
|
||||
*
|
||||
* @param initialValue The initial value given to the returned [State.value].
|
||||
* @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
|
||||
* flow.
|
||||
* @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
|
||||
* collection will stop if the lifecycle falls below that state, and will restart if it's in that
|
||||
* state again.
|
||||
* @param context [CoroutineContext] to use for collecting.
|
||||
*/
|
||||
@Composable
|
||||
fun <T> Flow<T>.collectAsStateWithLifecycle(
|
||||
initialValue: T,
|
||||
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
|
||||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
): State<T> = collectAsStateWithLifecycle(
|
||||
initialValue = initialValue,
|
||||
lifecycle = lifecycleOwner.lifecycle,
|
||||
minActiveState = minActiveState,
|
||||
context = context
|
||||
)
|
||||
|
||||
/**
|
||||
* Collects values from this [Flow] and represents its latest value via [State] in a
|
||||
* lifecycle-aware manner.
|
||||
*
|
||||
* Every time there would be new value posted into the [Flow] the returned [State] will be updated
|
||||
* causing recomposition of every [State.value] usage whenever the [lifecycle] is at
|
||||
* least [minActiveState].
|
||||
*
|
||||
* This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
|
||||
* state. The collection stops when [lifecycle] falls below [minActiveState].
|
||||
*
|
||||
* @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
|
||||
*
|
||||
* Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
|
||||
* parameter will throw an [IllegalArgumentException].
|
||||
*
|
||||
* @param initialValue The initial value given to the returned [State.value].
|
||||
* @param lifecycle [Lifecycle] used to restart collecting `this` flow.
|
||||
* @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
|
||||
* collection will stop if the lifecycle falls below that state, and will restart if it's in that
|
||||
* state again.
|
||||
* @param context [CoroutineContext] to use for collecting.
|
||||
*/
|
||||
@Composable
|
||||
fun <T> Flow<T>.collectAsStateWithLifecycle(
|
||||
initialValue: T,
|
||||
lifecycle: Lifecycle,
|
||||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
): State<T> {
|
||||
return produceState(initialValue, this, lifecycle, minActiveState, context) {
|
||||
lifecycle.repeatOnLifecycle(minActiveState) {
|
||||
if (context == EmptyCoroutineContext) {
|
||||
this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
|
||||
} else withContext(context) {
|
||||
this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2022 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.spa.framework.compose
|
||||
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
|
||||
/**
|
||||
* A flow which result is overridable.
|
||||
*/
|
||||
class OverridableFlow<T>(flow: Flow<T>) {
|
||||
private val overrideChannel = Channel<T>()
|
||||
|
||||
val flow = merge(overrideChannel.receiveAsFlow(), flow)
|
||||
|
||||
fun override(value: T) {
|
||||
overrideChannel.trySend(value)
|
||||
}
|
||||
}
|
||||
@@ -55,11 +55,6 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.spa.framework.compose.com.android.settingslib.spa.framework
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spa.framework.compose.OverridableFlow
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class OverridableFlowTest {
|
||||
|
||||
@Test
|
||||
fun noOverride() = runTest {
|
||||
val overridableFlow = OverridableFlow(flowOf(true))
|
||||
|
||||
launch {
|
||||
val values = collectValues(overridableFlow.flow)
|
||||
assertThat(values).containsExactly(true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenOverride() = runTest {
|
||||
val overridableFlow = OverridableFlow(flowOf(true))
|
||||
|
||||
overridableFlow.override(false)
|
||||
|
||||
launch {
|
||||
val values = collectValues(overridableFlow.flow)
|
||||
assertThat(values).containsExactly(true, false).inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> collectValues(flow: Flow<T>): List<T> = withTimeout(500) {
|
||||
val flowValues = mutableListOf<T>()
|
||||
flow.toList(flowValues)
|
||||
flowValues
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.framework.common
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.AppOpsManager
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.app.usage.StorageStatsManager
|
||||
import android.apphibernation.AppHibernationManager
|
||||
import android.content.Context
|
||||
import android.content.pm.verify.domain.DomainVerificationManager
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.permission.PermissionControllerManager
|
||||
|
||||
/** The [UserManager] instance. */
|
||||
val Context.userManager get() = getSystemService(UserManager::class.java)!!
|
||||
/** The [AlarmManager] instance. */
|
||||
val Context.alarmManager get() = getSystemService(AlarmManager::class.java)!!
|
||||
|
||||
/** The [AppHibernationManager] instance. */
|
||||
val Context.appHibernationManager get() = getSystemService(AppHibernationManager::class.java)!!
|
||||
|
||||
/** The [AppOpsManager] instance. */
|
||||
val Context.appOpsManager get() = getSystemService(AppOpsManager::class.java)!!
|
||||
|
||||
/** The [DevicePolicyManager] instance. */
|
||||
val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
|
||||
|
||||
/** The [StorageStatsManager] instance. */
|
||||
val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
|
||||
|
||||
/** The [DomainVerificationManager] instance. */
|
||||
val Context.domainVerificationManager
|
||||
get() = getSystemService(DomainVerificationManager::class.java)!!
|
||||
|
||||
/** The [PermissionControllerManager] instance. */
|
||||
val Context.permissionControllerManager
|
||||
get() = getSystemService(PermissionControllerManager::class.java)!!
|
||||
|
||||
/** The [StorageStatsManager] instance. */
|
||||
val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
|
||||
|
||||
/** The [UserManager] instance. */
|
||||
val Context.userManager get() = getSystemService(UserManager::class.java)!!
|
||||
|
||||
/** Gets a new [Context] for the given [UserHandle]. */
|
||||
fun Context.asUser(userHandle: UserHandle): Context = createContextAsUser(userHandle, 0)
|
||||
|
||||
Reference in New Issue
Block a user