Loading packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt 0 → 100644 +28 −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.spa.lifecycle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.flow.Flow @Composable fun <T> Flow<T>.collectAsCallbackWithLifecycle(): () -> T? { val value by collectAsStateWithLifecycle(initialValue = null) return { value } } packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt 0 → 100644 +64 −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.spa.lifecycle import androidx.compose.material3.Text import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.waitUntilExists import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FlowExtTest { @get:Rule val composeTestRule = createComposeRule() @Test fun collectAsCallbackWithLifecycle() { val flow = flowOf(TEXT) composeTestRule.setContent { val callback = flow.collectAsCallbackWithLifecycle() Text(callback() ?: "") } composeTestRule.waitUntilExists(hasText(TEXT)) } @Test fun collectAsCallbackWithLifecycle_changed() { val flow = MutableStateFlow(TEXT) composeTestRule.setContent { val callback = flow.collectAsCallbackWithLifecycle() Text(callback() ?: "") } flow.value = NEW_TEXT composeTestRule.waitUntilExists(hasText(NEW_TEXT)) } private companion object { const val TEXT = "Text" const val NEW_TEXT = "New Text" } } packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt +8 −19 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * 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. Loading @@ -16,7 +16,7 @@ package com.android.settingslib.spaprivileged.model.app import android.app.AppOpsManager; import android.app.AppOpsManager import android.app.AppOpsManager.MODE_ALLOWED import android.app.AppOpsManager.MODE_ERRORED import android.app.AppOpsManager.Mode Loading @@ -24,14 +24,13 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.UserHandle import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import com.android.settingslib.spaprivileged.framework.common.appOpsManager import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map interface IAppOpsController { val mode: LiveData<Int> val isAllowed: LiveData<Boolean> val mode: Flow<Int> val isAllowed: Flow<Boolean> get() = mode.map { it == MODE_ALLOWED } fun setAllowed(allowed: Boolean) Loading @@ -48,9 +47,7 @@ class AppOpsController( ) : IAppOpsController { private val appOpsManager = context.appOpsManager private val packageManager = context.packageManager override val mode: LiveData<Int> get() = _mode override val mode = appOpsManager.opModeFlow(op, app) override fun setAllowed(allowed: Boolean) { val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed Loading @@ -68,15 +65,7 @@ class AppOpsController( PackageManager.FLAG_PERMISSION_USER_SET, UserHandle.getUserHandleForUid(app.uid)) } _mode.postValue(mode) } @Mode override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName) private val _mode = object : MutableLiveData<Int>() { override fun onActive() { postValue(getMode()) } } @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app) } packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt 0 → 100644 +46 −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.model.app import android.app.AppOpsManager import android.content.pm.ApplicationInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map fun AppOpsManager.getOpMode(op: Int, app: ApplicationInfo) = checkOpNoThrow(op, app.uid, app.packageName) fun AppOpsManager.opModeFlow(op: Int, app: ApplicationInfo) = opChangedFlow(op, app).map { getOpMode(op, app) }.flowOn(Dispatchers.Default) private fun AppOpsManager.opChangedFlow(op: Int, app: ApplicationInfo) = callbackFlow { val listener = object : AppOpsManager.OnOpChangedListener { override fun onOpChanged(op: String, packageName: String) {} override fun onOpChanged(op: String, packageName: String, userId: Int) { if (userId == app.userId) trySend(Unit) } } startWatchingMode(op, app.packageName, listener) trySend(Unit) awaitClose { stopWatchingMode(listener) } }.conflate().flowOn(Dispatchers.Default) packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +2 −2 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settingslib.spa.framework.util.asyncMapItem import com.android.settingslib.spa.framework.util.filterItem import com.android.settingslib.spaprivileged.model.app.AppOpsController Loading Loading @@ -166,7 +166,7 @@ internal fun isAllowed( return { true } } val mode = appOpsController.mode.observeAsState() val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null) return { when (mode.value) { null -> null Loading Loading
packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt 0 → 100644 +28 −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.spa.lifecycle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.flow.Flow @Composable fun <T> Flow<T>.collectAsCallbackWithLifecycle(): () -> T? { val value by collectAsStateWithLifecycle(initialValue = null) return { value } }
packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt 0 → 100644 +64 −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.spa.lifecycle import androidx.compose.material3.Text import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.waitUntilExists import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FlowExtTest { @get:Rule val composeTestRule = createComposeRule() @Test fun collectAsCallbackWithLifecycle() { val flow = flowOf(TEXT) composeTestRule.setContent { val callback = flow.collectAsCallbackWithLifecycle() Text(callback() ?: "") } composeTestRule.waitUntilExists(hasText(TEXT)) } @Test fun collectAsCallbackWithLifecycle_changed() { val flow = MutableStateFlow(TEXT) composeTestRule.setContent { val callback = flow.collectAsCallbackWithLifecycle() Text(callback() ?: "") } flow.value = NEW_TEXT composeTestRule.waitUntilExists(hasText(NEW_TEXT)) } private companion object { const val TEXT = "Text" const val NEW_TEXT = "New Text" } }
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt +8 −19 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * 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. Loading @@ -16,7 +16,7 @@ package com.android.settingslib.spaprivileged.model.app import android.app.AppOpsManager; import android.app.AppOpsManager import android.app.AppOpsManager.MODE_ALLOWED import android.app.AppOpsManager.MODE_ERRORED import android.app.AppOpsManager.Mode Loading @@ -24,14 +24,13 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.UserHandle import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import com.android.settingslib.spaprivileged.framework.common.appOpsManager import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map interface IAppOpsController { val mode: LiveData<Int> val isAllowed: LiveData<Boolean> val mode: Flow<Int> val isAllowed: Flow<Boolean> get() = mode.map { it == MODE_ALLOWED } fun setAllowed(allowed: Boolean) Loading @@ -48,9 +47,7 @@ class AppOpsController( ) : IAppOpsController { private val appOpsManager = context.appOpsManager private val packageManager = context.packageManager override val mode: LiveData<Int> get() = _mode override val mode = appOpsManager.opModeFlow(op, app) override fun setAllowed(allowed: Boolean) { val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed Loading @@ -68,15 +65,7 @@ class AppOpsController( PackageManager.FLAG_PERMISSION_USER_SET, UserHandle.getUserHandleForUid(app.uid)) } _mode.postValue(mode) } @Mode override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName) private val _mode = object : MutableLiveData<Int>() { override fun onActive() { postValue(getMode()) } } @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app) }
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt 0 → 100644 +46 −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.model.app import android.app.AppOpsManager import android.content.pm.ApplicationInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map fun AppOpsManager.getOpMode(op: Int, app: ApplicationInfo) = checkOpNoThrow(op, app.uid, app.packageName) fun AppOpsManager.opModeFlow(op: Int, app: ApplicationInfo) = opChangedFlow(op, app).map { getOpMode(op, app) }.flowOn(Dispatchers.Default) private fun AppOpsManager.opChangedFlow(op: Int, app: ApplicationInfo) = callbackFlow { val listener = object : AppOpsManager.OnOpChangedListener { override fun onOpChanged(op: String, packageName: String) {} override fun onOpChanged(op: String, packageName: String, userId: Int) { if (userId == app.userId) trySend(Unit) } } startWatchingMode(op, app.packageName, listener) trySend(Unit) awaitClose { stopWatchingMode(listener) } }.conflate().flowOn(Dispatchers.Default)
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +2 −2 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settingslib.spa.framework.util.asyncMapItem import com.android.settingslib.spa.framework.util.filterItem import com.android.settingslib.spaprivileged.model.app.AppOpsController Loading Loading @@ -166,7 +166,7 @@ internal fun isAllowed( return { true } } val mode = appOpsController.mode.observeAsState() val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null) return { when (mode.value) { null -> null Loading