Loading packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * 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 import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver /** * A [BroadcastReceiver] which registered when on start and unregistered when on stop. */ @Composable fun DisposableBroadcastReceiverAsUser( userId: Int, intentFilter: IntentFilter, onReceive: (Intent) -> Unit, ) { val broadcastReceiver = remember { object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { onReceive(intent) } } } val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { context.registerReceiverAsUser( broadcastReceiver, UserHandle.of(userId), intentFilter, null, null) } else if (event == Lifecycle.Event.ON_STOP) { context.unregisterReceiver(broadcastReceiver) } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } } packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt +16 −8 Original line number Diff line number Diff line Loading @@ -19,25 +19,33 @@ package com.android.settingslib.spa.framework.util import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.snapshotFlow import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map /** * Returns a [Flow] whose values are a list which containing the results of asynchronously applying * the given [transform] function to each element in the original flow's list. */ inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> = map { list -> list.asyncMap(transform) } @OptIn(ExperimentalCoroutinesApi::class) inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> = flatMapLatest { snapshotFlow { block(it).value } } /** * Delays the flow a little bit, wait the other flow's first value. */ fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> = combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value } fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> = combine(flow.distinctUntilChangedBy {}) { value, _ -> value } /** * Returns a [Flow] whose values are generated list by combining the most recently emitted non null * values by each flow. */ inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine( flows.asList(), ) { array: Array<T?> -> array.filterNotNull() } class StateFlowBridge<T> { private val stateFlow = MutableStateFlow<T?>(null) Loading packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +9 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.settingslib.spa.widget.button import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize Loading @@ -39,6 +40,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip Loading @@ -54,6 +56,7 @@ import com.android.settingslib.spa.framework.theme.divider data class ActionButton( val text: String, val imageVector: ImageVector, val enabled: Boolean = true, val onClick: () -> Unit, ) Loading @@ -79,10 +82,15 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { modifier = Modifier .weight(1f) .fillMaxHeight(), enabled = actionButton.enabled, // Because buttons could appear, disappear or change positions, reset the interaction source // to prevent highlight the wrong button. interactionSource = remember(actionButton) { MutableInteractionSource() }, shape = RectangleShape, colors = ButtonDefaults.filledTonalButtonColors( containerColor = MaterialTheme.colorScheme.surface, containerColor = SettingsTheme.colorScheme.surface, contentColor = SettingsTheme.colorScheme.categoryTitle, disabledContainerColor = SettingsTheme.colorScheme.surface, ), contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp), ) { Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt +11 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,23 @@ package com.android.settingslib.spaprivileged.model.app import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.UserHandle /** The user id for a given application. */ val ApplicationInfo.userId: Int get() = UserHandle.getUserId(uid) /** The [UserHandle] for a given application. */ val ApplicationInfo.userHandle: UserHandle get() = UserHandle.getUserHandleForUid(uid) /** Checks whether a flag is associated with the application. */ fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0 /** Checks whether the application is disabled until used. */ fun ApplicationInfo.isDisabledUntilUsed(): Boolean = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED /** Converts to the route string which used in navigation. */ fun ApplicationInfo.toRoute() = "$packageName/$userId" packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt +1 −1 Original line number Diff line number Diff line Loading @@ -55,7 +55,7 @@ object PackageManagers { iPackageManager.isPackageAvailable(it, userId) }.toSet() private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = try { PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId) } catch (e: PackageManager.NameNotFoundException) { Loading Loading
packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * 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 import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver /** * A [BroadcastReceiver] which registered when on start and unregistered when on stop. */ @Composable fun DisposableBroadcastReceiverAsUser( userId: Int, intentFilter: IntentFilter, onReceive: (Intent) -> Unit, ) { val broadcastReceiver = remember { object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { onReceive(intent) } } } val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { context.registerReceiverAsUser( broadcastReceiver, UserHandle.of(userId), intentFilter, null, null) } else if (event == Lifecycle.Event.ON_STOP) { context.unregisterReceiver(broadcastReceiver) } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } }
packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt +16 −8 Original line number Diff line number Diff line Loading @@ -19,25 +19,33 @@ package com.android.settingslib.spa.framework.util import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.snapshotFlow import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map /** * Returns a [Flow] whose values are a list which containing the results of asynchronously applying * the given [transform] function to each element in the original flow's list. */ inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> = map { list -> list.asyncMap(transform) } @OptIn(ExperimentalCoroutinesApi::class) inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> = flatMapLatest { snapshotFlow { block(it).value } } /** * Delays the flow a little bit, wait the other flow's first value. */ fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> = combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value } fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> = combine(flow.distinctUntilChangedBy {}) { value, _ -> value } /** * Returns a [Flow] whose values are generated list by combining the most recently emitted non null * values by each flow. */ inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine( flows.asList(), ) { array: Array<T?> -> array.filterNotNull() } class StateFlowBridge<T> { private val stateFlow = MutableStateFlow<T?>(null) Loading
packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +9 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.settingslib.spa.widget.button import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize Loading @@ -39,6 +40,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip Loading @@ -54,6 +56,7 @@ import com.android.settingslib.spa.framework.theme.divider data class ActionButton( val text: String, val imageVector: ImageVector, val enabled: Boolean = true, val onClick: () -> Unit, ) Loading @@ -79,10 +82,15 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { modifier = Modifier .weight(1f) .fillMaxHeight(), enabled = actionButton.enabled, // Because buttons could appear, disappear or change positions, reset the interaction source // to prevent highlight the wrong button. interactionSource = remember(actionButton) { MutableInteractionSource() }, shape = RectangleShape, colors = ButtonDefaults.filledTonalButtonColors( containerColor = MaterialTheme.colorScheme.surface, containerColor = SettingsTheme.colorScheme.surface, contentColor = SettingsTheme.colorScheme.categoryTitle, disabledContainerColor = SettingsTheme.colorScheme.surface, ), contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp), ) { Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt +11 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,23 @@ package com.android.settingslib.spaprivileged.model.app import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.UserHandle /** The user id for a given application. */ val ApplicationInfo.userId: Int get() = UserHandle.getUserId(uid) /** The [UserHandle] for a given application. */ val ApplicationInfo.userHandle: UserHandle get() = UserHandle.getUserHandleForUid(uid) /** Checks whether a flag is associated with the application. */ fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0 /** Checks whether the application is disabled until used. */ fun ApplicationInfo.isDisabledUntilUsed(): Boolean = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED /** Converts to the route string which used in navigation. */ fun ApplicationInfo.toRoute() = "$packageName/$userId"
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt +1 −1 Original line number Diff line number Diff line Loading @@ -55,7 +55,7 @@ object PackageManagers { iPackageManager.isPackageAvailable(it, userId) }.toSet() private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = try { PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId) } catch (e: PackageManager.NameNotFoundException) { Loading