From f9078e0e3659c2576b4244119ac866620dd326ea Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 2 Dec 2022 16:04:50 +0800 Subject: [PATCH 1/3] Add tests for Flows & StateFlowBridge Also move test utils to the correct folder. Bug: 260660819 Test: unit tests Change-Id: I74a49047c644475f7b0be3bf6b8308b4bb353e80 --- .../settingslib/spa/framework/util/Flows.kt | 41 ++------- .../spa/framework/util/StateFlowBridge.kt | 42 +++++++++ .../spa/framework/util/FlowsTest.kt | 90 +++++++++++++++++++ .../spa/framework/util/StateFlowBridgeTest.kt | 67 ++++++++++++++ .../settingslib/spa/testutils/FlowTestUtil.kt | 26 ++++++ .../spa/testutils}/MockitoHelper.kt | 0 .../settingslib/spa/testutils}/SpaTest.kt | 0 7 files changed, 233 insertions(+), 33 deletions(-) create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt create mode 100644 packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt create mode 100644 packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt create mode 100644 packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt rename packages/SettingsLib/Spa/testutils/src/{ => com/android/settingslib/spa/testutils}/MockitoHelper.kt (100%) rename packages/SettingsLib/Spa/testutils/src/{ => com/android/settingslib/spa/testutils}/SpaTest.kt (100%) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt index 97e3ac2147ca0..8bfcff811dd6d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt @@ -16,15 +16,10 @@ package com.android.settingslib.spa.framework.util -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State 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.map +import kotlinx.coroutines.flow.take /** * Returns a [Flow] whose values are a list which containing the results of applying the given @@ -40,34 +35,14 @@ inline fun Flow>.mapItem(crossinline transform: (T) -> R): Flow Flow>.asyncMapItem(crossinline transform: (T) -> R): Flow> = map { list -> list.asyncMap(transform) } +/** + * Returns a [Flow] whose values are a list containing only elements matching the given [predicate]. + */ +inline fun Flow>.filterItem(crossinline predicate: (T) -> Boolean): Flow> = + map { list -> list.filter(predicate) } + /** * Delays the flow a little bit, wait the other flow's first value. */ fun Flow.waitFirst(otherFlow: Flow): Flow = - combine(otherFlow.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 combineToList(vararg flows: Flow): Flow> = combine( - flows.asList(), -) { array: Array -> array.filterNotNull() } - -class StateFlowBridge { - private val stateFlow = MutableStateFlow(null) - val flow = stateFlow.filterNotNull() - - fun setIfAbsent(value: T) { - if (stateFlow.value == null) { - stateFlow.value = value - } - } - - @Composable - fun Sync(state: State) { - LaunchedEffect(state.value) { - stateFlow.value = state.value - } - } -} + combine(otherFlow.take(1)) { value, _ -> value } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt new file mode 100644 index 0000000000000..494e69b657ca4 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt @@ -0,0 +1,42 @@ +/* + * 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.util + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull + +/** A StateFlow holder which value could be set or sync from [State]. */ +class StateFlowBridge { + private val stateFlow = MutableStateFlow(null) + val flow = stateFlow.filterNotNull() + + fun setIfAbsent(value: T) { + if (stateFlow.value == null) { + stateFlow.value = value + } + } + + @Composable + fun Sync(state: State) { + LaunchedEffect(state.value) { + stateFlow.value = state.value + } + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt new file mode 100644 index 0000000000000..4dcdea96ac9d0 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt @@ -0,0 +1,90 @@ +/* + * 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.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.count +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class FlowsTest { + @Test + fun mapItem() = runTest { + val inputFlow = flowOf(listOf("A", "BB", "CCC")) + + val outputFlow = inputFlow.mapItem { it.length } + + assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder() + } + + @Test + fun asyncMapItem() = runTest { + val inputFlow = flowOf(listOf("A", "BB", "CCC")) + + val outputFlow = inputFlow.asyncMapItem { it.length } + + assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder() + } + + @Test + fun filterItem() = runTest { + val inputFlow = flowOf(listOf("A", "BB", "CCC")) + + val outputFlow = inputFlow.filterItem { it.length >= 2 } + + assertThat(outputFlow.first()).containsExactly("BB", "CCC").inOrder() + } + + @Test + fun waitFirst_otherFlowEmpty() = runTest { + val mainFlow = flowOf("A") + val otherFlow = emptyFlow() + + val outputFlow = mainFlow.waitFirst(otherFlow) + + assertThat(outputFlow.count()).isEqualTo(0) + } + + @Test + fun waitFirst_otherFlowOneValue() = runTest { + val mainFlow = flowOf("A") + val otherFlow = flowOf("B") + + val outputFlow = mainFlow.waitFirst(otherFlow) + + assertThat(outputFlow.toList()).containsExactly("A") + } + + @Test + fun waitFirst_otherFlowTwoValues() = runTest { + val mainFlow = flowOf("A") + val otherFlow = flowOf("B", "B") + + val outputFlow = mainFlow.waitFirst(otherFlow) + + assertThat(outputFlow.toList()).containsExactly("A") + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt new file mode 100644 index 0000000000000..e1d9a28a90457 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt @@ -0,0 +1,67 @@ +/* + * 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.util + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class StateFlowBridgeTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun stateFlowBridge_initial() = runTest { + val stateFlowBridge = StateFlowBridge() + + val flow = stateFlowBridge.flow + + val first = flow.firstWithTimeoutOrNull() + assertThat(first).isNull() + } + + @Test + fun stateFlowBridge_setIfAbsent() = runTest { + val stateFlowBridge = StateFlowBridge() + + stateFlowBridge.setIfAbsent("A") + + val first = stateFlowBridge.flow.firstWithTimeoutOrNull() + assertThat(first).isEqualTo("A") + } + + @Test + fun stateFlowBridge_sync() = runTest { + val stateFlowBridge = StateFlowBridge() + + composeTestRule.setContent { + stateFlowBridge.Sync(stateOf("A")) + } + + val first = stateFlowBridge.flow.firstWithTimeoutOrNull() + assertThat(first).isEqualTo("A") + } +} diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt new file mode 100644 index 0000000000000..7a114997c44ca --- /dev/null +++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt @@ -0,0 +1,26 @@ +/* + * 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.testutils + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withTimeoutOrNull + +suspend fun Flow.firstWithTimeoutOrNull(timeMillis: Long = 500): T? = + withTimeoutOrNull(timeMillis) { + first() + } diff --git a/packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt similarity index 100% rename from packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt rename to packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt diff --git a/packages/SettingsLib/Spa/testutils/src/SpaTest.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt similarity index 100% rename from packages/SettingsLib/Spa/testutils/src/SpaTest.kt rename to packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt From e3bda5623ba3d3c736311c116288a23777e4051e Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Sun, 4 Dec 2022 19:52:39 +0800 Subject: [PATCH 2/3] Hide not changeable app from AppOpPermissionAppList Also add unit tests for AppOpPermissionAppList. Also correct the test only resources R to com.android.settingslib.spaprivileged.test.R Bug: 260660819 Test: Unit test Test: Manually with Settings Change-Id: I732f777906c5ea4a79e876b982ba0ca28b39a0ee --- .../model/app/AppOpsController.kt | 28 +- .../template/app/AppOpPermissionAppList.kt | 26 +- .../SpaPrivileged/tests/AndroidManifest.xml | 4 +- .../tests/res/values/strings.xml | 11 +- .../spaprivileged/template/app/AppInfoTest.kt | 2 +- .../template/app/AppListPageTest.kt | 2 +- .../spaprivileged/template/app/AppListTest.kt | 2 +- .../app/AppOpPermissionAppListTest.kt | 264 ++++++++++++++++++ .../template/app/AppStorageSizeTest.kt | 2 +- .../app/TogglePermissionAppListPageTest.kt | 4 +- .../app/TogglePermissionAppListTest.kt | 4 +- .../TestTogglePermissionAppListModel.kt | 2 +- 12 files changed, 317 insertions(+), 34 deletions(-) create mode 100644 packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt index 71cf23c61fa1c..c08169e888520 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt @@ -16,7 +16,6 @@ package com.android.settingslib.spaprivileged.model.app -import android.app.AppOpsManager import android.app.AppOpsManager.MODE_ALLOWED import android.app.AppOpsManager.MODE_ERRORED import android.app.AppOpsManager.Mode @@ -25,34 +24,41 @@ import android.content.pm.ApplicationInfo import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map +import com.android.settingslib.spaprivileged.framework.common.appOpsManager + +interface IAppOpsController { + val mode: LiveData + val isAllowed: LiveData + get() = mode.map { it == MODE_ALLOWED } + + fun setAllowed(allowed: Boolean) + + @Mode + fun getMode(): Int +} class AppOpsController( context: Context, private val app: ApplicationInfo, private val op: Int, -) { - private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java)) +) : IAppOpsController { + private val appOpsManager = context.appOpsManager - val mode: LiveData + override val mode: LiveData get() = _mode - val isAllowed: LiveData - get() = _mode.map { it == MODE_ALLOWED } - fun setAllowed(allowed: Boolean) { + override fun setAllowed(allowed: Boolean) { val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED appOpsManager.setMode(op, app.uid, app.packageName, mode) _mode.postValue(mode) } @Mode - fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName) + override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName) private val _mode = object : MutableLiveData() { override fun onActive() { postValue(getMode()) } - - override fun onInactive() { - } } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt index c6f41d3bd3e2c..a3578321a0986 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt @@ -25,11 +25,12 @@ import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember +import com.android.settingslib.spa.framework.util.filterItem import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.IAppOpsController +import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers -import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission -import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map @@ -37,21 +38,24 @@ import kotlinx.coroutines.flow.map data class AppOpPermissionRecord( override val app: ApplicationInfo, val hasRequestPermission: Boolean, - var appOpsController: AppOpsController, + var appOpsController: IAppOpsController, ) : AppRecord -abstract class AppOpPermissionListModel(private val context: Context) : - TogglePermissionAppListModel { +abstract class AppOpPermissionListModel( + private val context: Context, + private val packageManagers: IPackageManagers = PackageManagers, +) : TogglePermissionAppListModel { abstract val appOp: Int abstract val permission: String + /** These not changeable packages will also be hidden from app list. */ private val notChangeablePackages = setOf("android", "com.android.systemui", context.packageName) override fun transform(userIdFlow: Flow, appListFlow: Flow>) = userIdFlow.map { userId -> - PackageManagers.getAppOpPermissionPackages(userId, permission) + packageManagers.getAppOpPermissionPackages(userId, permission) }.combine(appListFlow) { packageNames, appList -> appList.map { app -> AppOpPermissionRecord( @@ -64,14 +68,12 @@ abstract class AppOpPermissionListModel(private val context: Context) : override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord( app = app, - hasRequestPermission = app.hasRequestPermission(permission), + hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) }, appOpsController = AppOpsController(context = context, app = app, op = appOp), ) override fun filter(userIdFlow: Flow, recordListFlow: Flow>) = - recordListFlow.map { recordList -> - recordList.filter { it.hasRequestPermission } - } + recordListFlow.filterItem(::isChangeable) /** * Defining the default behavior as permissible as long as the package requested this permission @@ -85,7 +87,9 @@ abstract class AppOpPermissionListModel(private val context: Context) : when (mode.value) { null -> null MODE_ALLOWED -> true - MODE_DEFAULT -> record.app.hasGrantPermission(permission) + MODE_DEFAULT -> with(packageManagers) { + record.app.hasGrantPermission(permission) + } else -> false } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml index c4f490ed398b0..8d384e8ca02e7 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml +++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml @@ -15,7 +15,7 @@ --> + package="com.android.settingslib.spaprivileged.test"> @@ -24,5 +24,5 @@ + android:targetPackage="com.android.settingslib.spaprivileged.test" /> diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml index fb1e09acddf5b..bdc0ba8224dec 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml +++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml @@ -22,5 +22,14 @@ Allow Test Permission - Test Permission is for demo. + Test Permission is for testing. + + + Test App Op Permission + + + Allow Test App Op Permission + + + Test App Op Permission is for testing. diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt index 8ca7950905afb..bb56c10b28e36 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt @@ -36,7 +36,7 @@ class AppInfoTest { @get:Rule val composeTestRule = createComposeRule() - private var context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = ApplicationProvider.getApplicationContext() @Test fun appInfoLabel_isDisplayed() { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt index c3c96c645a155..946dc2a95d24b 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt @@ -41,7 +41,7 @@ class AppListPageTest { @get:Rule val composeTestRule = createComposeRule() - private var context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = ApplicationProvider.getApplicationContext() @Test fun title_isDisplayed() { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt index df80dd4007dac..945bd510d15b8 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt @@ -44,7 +44,7 @@ class AppListTest { @get:Rule val composeTestRule = createComposeRule() - private var context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = ApplicationProvider.getApplicationContext() @Test fun whenNoApps() { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt new file mode 100644 index 0000000000000..f1d9abe86804e --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt @@ -0,0 +1,264 @@ +/* + * 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.template.app + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.State +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.lifecycle.liveData +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spaprivileged.model.app.IAppOpsController +import com.android.settingslib.spaprivileged.model.app.IPackageManagers +import com.android.settingslib.spaprivileged.test.R +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class AppOpPermissionAppListTest { + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var packageManagers: IPackageManagers + + private lateinit var listModel: TestAppOpPermissionAppListModel + + @Before + fun setUp() = runTest { + whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION)) + .thenReturn(emptySet()) + listModel = TestAppOpPermissionAppListModel() + } + + @Test + fun transformItem_recordHasCorrectApp() { + val record = listModel.transformItem(APP) + + assertThat(record.app).isSameInstanceAs(APP) + } + + @Test + fun transformItem_hasRequestPermission() = runTest { + with(packageManagers) { + whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true) + } + + val record = listModel.transformItem(APP) + + assertThat(record.hasRequestPermission).isTrue() + } + + @Test + fun transformItem_notRequestPermission() = runTest { + with(packageManagers) { + whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) + } + + val record = listModel.transformItem(APP) + + assertThat(record.hasRequestPermission).isFalse() + } + + @Test + fun filter() = runTest { + with(packageManagers) { + whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) + } + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = false, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + ) + + val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record))) + + val recordList = recordListFlow.firstWithTimeoutOrNull()!! + assertThat(recordList).isEmpty() + } + + @Test + fun isAllowed_allowed() { + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = true, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED), + ) + + val isAllowed = getIsAllowed(record) + + assertThat(isAllowed).isTrue() + } + + @Test + fun isAllowed_defaultAndHasGrantPermission() { + with(packageManagers) { + whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) + } + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = true, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + ) + + val isAllowed = getIsAllowed(record) + + assertThat(isAllowed).isTrue() + } + + @Test + fun isAllowed_defaultAndNotGrantPermission() { + with(packageManagers) { + whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) + } + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = true, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + ) + + val isAllowed = getIsAllowed(record) + + assertThat(isAllowed).isFalse() + } + + @Test + fun isAllowed_notAllowed() { + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = true, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), + ) + + val isAllowed = getIsAllowed(record) + + assertThat(isAllowed).isFalse() + } + + @Test + fun isChangeable_notRequestPermission() { + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = false, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + ) + + val isChangeable = listModel.isChangeable(record) + + assertThat(isChangeable).isFalse() + } + + @Test + fun isChangeable_notChangeablePackages() { + val record = AppOpPermissionRecord( + app = NOT_CHANGEABLE_APP, + hasRequestPermission = true, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + ) + + val isChangeable = listModel.isChangeable(record) + + assertThat(isChangeable).isFalse() + } + + @Test + fun isChangeable_hasRequestPermissionAndChangeable() { + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = true, + appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + ) + + val isChangeable = listModel.isChangeable(record) + + assertThat(isChangeable).isTrue() + } + + @Test + fun setAllowed() { + val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) + val record = AppOpPermissionRecord( + app = APP, + hasRequestPermission = true, + appOpsController = appOpsController, + ) + + listModel.setAllowed(record = record, newAllowed = true) + + assertThat(appOpsController.setAllowedCalledWith).isTrue() + } + + private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? { + lateinit var isAllowedState: State + composeTestRule.setContent { + isAllowedState = listModel.isAllowed(record) + } + return isAllowedState.value + } + + private inner class TestAppOpPermissionAppListModel : + AppOpPermissionListModel(context, packageManagers) { + override val pageTitleResId = R.string.test_app_op_permission_title + override val switchTitleResId = R.string.test_app_op_permission_switch_title + override val footerResId = R.string.test_app_op_permission_footer + override val appOp = AppOpsManager.OP_MANAGE_MEDIA + override val permission = PERMISSION + } + + private companion object { + const val USER_ID = 0 + const val PACKAGE_NAME = "package.name" + const val PERMISSION = "PERMISSION" + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + val NOT_CHANGEABLE_APP = ApplicationInfo().apply { + packageName = "android" + } + } +} + +private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController { + var setAllowedCalledWith: Boolean? = null + + override val mode = liveData { emit(fakeMode) } + + override fun setAllowed(allowed: Boolean) { + setAllowedCalledWith = allowed + } + + override fun getMode() = fakeMode +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt index 8e98d8cd6975d..066e28a4d0085 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt @@ -48,7 +48,7 @@ class AppStorageSizeTest { val composeTestRule = createComposeRule() @Spy - private var context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = ApplicationProvider.getApplicationContext() @Mock private lateinit var storageStatsManager: StorageStatsManager diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt index 4bc612abb1a43..355dfb6b3ae17 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.test.R import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -36,7 +36,7 @@ class TogglePermissionAppListPageTest { @get:Rule val composeTestRule = createComposeRule() - private var context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = ApplicationProvider.getApplicationContext() @Test fun appListInjectEntry_titleDisplayed() { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt index af3189f2ec414..2d169e0357fe7 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.test.R import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -36,7 +36,7 @@ class TogglePermissionAppListTest { @get:Rule val composeTestRule = createComposeRule() - private var context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = ApplicationProvider.getApplicationContext() @Test fun appListInjectEntry_titleDisplayed() { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt index 91a9c6b620af8..64bc11f0bac57 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt @@ -19,7 +19,7 @@ package com.android.settingslib.spaprivileged.tests.testutils import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import com.android.settingslib.spa.framework.compose.stateOf -import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.test.R import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel import kotlinx.coroutines.flow.Flow From 7459ecff6e0ab1e2e746ed7c481e84660a31b384 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 5 Dec 2022 00:05:48 +0800 Subject: [PATCH 3/3] Refactor TogglePermissionAppInfoPageProvider Also add unit tests. Bug: 260660819 Test: Unit test Test: Manually with Settings Change-Id: Ifb6844f4127f07ea5d211d68806e683144310589 --- .../model/enterprise/RestrictionsProvider.kt | 2 + .../spaprivileged/template/app/AppInfoPage.kt | 19 ++- .../app/TogglePermissionAppInfoPage.kt | 77 ++++----- .../preference/RestrictedSwitchPreference.kt | 11 +- .../app/TogglePermissionAppInfoPageTest.kt | 153 ++++++++++++++++++ .../app/TogglePermissionAppListTest.kt | 7 +- .../RestrictedSwitchPreferenceTest.kt | 2 +- .../TestTogglePermissionAppListModel.kt | 9 +- .../TestTogglePermissionAppListProvider.kt | 25 +++ 9 files changed, 235 insertions(+), 70 deletions(-) create mode 100644 packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt create mode 100644 packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index a618c3d0575b0..ae362c894e6db 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -74,6 +74,8 @@ interface RestrictionsProvider { fun restrictedModeState(): State } +typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider + internal class RestrictionsProviderImpl( private val context: Context, private val restrictions: Restrictions, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt index 8b19c5b0e3195..0b45da67eaf73 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt @@ -16,11 +16,12 @@ package com.android.settingslib.spaprivileged.template.app +import android.content.pm.PackageInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Footer -import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.model.app.IPackageManagers @Composable fun AppInfoPage( @@ -28,18 +29,16 @@ fun AppInfoPage( packageName: String, userId: Int, footerText: String, - content: @Composable () -> Unit, + packageManagers: IPackageManagers, + content: @Composable PackageInfo.() -> Unit, ) { + val packageInfo = remember(packageName, userId) { + packageManagers.getPackageInfoAsUser(packageName, userId) + } ?: return RegularScaffold(title = title) { - val appInfoProvider = remember { - PackageManagers.getPackageInfoAsUser(packageName, userId)?.let { packageInfo -> - AppInfoProvider(packageInfo) - } - } ?: return@RegularScaffold + remember(packageInfo) { AppInfoProvider(packageInfo) }.AppInfo(displayVersion = true) - appInfoProvider.AppInfo(displayVersion = true) - - content() + packageInfo.content() Footer(footerText) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 8287693c90ee0..5ae5adaff634c 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.template.app import android.content.Context import android.content.pm.ApplicationInfo import android.os.Bundle +import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -38,23 +39,15 @@ import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.model.app.toRoute import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference import kotlinx.coroutines.Dispatchers -private const val ENTRY_NAME = "AllowControl" -private const val PERMISSION = "permission" -private const val PACKAGE_NAME = "rt_packageName" -private const val USER_ID = "rt_userId" -private const val PAGE_NAME = "TogglePermissionAppInfoPage" -private val PAGE_PARAMETER = listOf( - navArgument(PERMISSION) { type = NavType.StringType }, - navArgument(PACKAGE_NAME) { type = NavType.StringType }, - navArgument(USER_ID) { type = NavType.IntType }, -) - internal class TogglePermissionAppInfoPageProvider( private val appListTemplate: TogglePermissionAppListTemplate, ) : SettingsPageProvider { @@ -64,11 +57,7 @@ internal class TogglePermissionAppInfoPageProvider( override fun buildEntry(arguments: Bundle?): List { val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments) - val entryList = mutableListOf() - entryList.add( - SettingsEntryBuilder.create(ENTRY_NAME, owner).build() - ) - return entryList + return listOf(SettingsEntryBuilder.create("AllowControl", owner).build()) } @Composable @@ -76,11 +65,22 @@ internal class TogglePermissionAppInfoPageProvider( val permissionType = arguments?.getString(PERMISSION)!! val packageName = arguments.getString(PACKAGE_NAME)!! val userId = arguments.getInt(USER_ID) - val listModel = appListTemplate.rememberModel(permissionType) - TogglePermissionAppInfoPage(listModel, packageName, userId) + appListTemplate.rememberModel(permissionType) + .TogglePermissionAppInfoPage(packageName, userId) } companion object { + private const val PAGE_NAME = "TogglePermissionAppInfoPage" + private const val PERMISSION = "permission" + private const val PACKAGE_NAME = "rt_packageName" + private const val USER_ID = "rt_userId" + + private val PAGE_PARAMETER = listOf( + navArgument(PERMISSION) { type = NavType.StringType }, + navArgument(PACKAGE_NAME) { type = NavType.StringType }, + navArgument(USER_ID) { type = NavType.IntType }, + ) + @Composable fun navigator(permissionType: String, app: ApplicationInfo) = navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}") @@ -116,43 +116,36 @@ internal class TogglePermissionAppInfoPageProvider( } } +@VisibleForTesting @Composable -private fun TogglePermissionAppInfoPage( - listModel: TogglePermissionAppListModel, +internal fun TogglePermissionAppListModel.TogglePermissionAppInfoPage( packageName: String, userId: Int, + packageManagers: IPackageManagers = PackageManagers, + restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl, ) { AppInfoPage( - title = stringResource(listModel.pageTitleResId), + title = stringResource(pageTitleResId), packageName = packageName, userId = userId, - footerText = stringResource(listModel.footerResId), + footerText = stringResource(footerResId), + packageManagers = packageManagers, ) { - val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage - LaunchedEffect(model, Dispatchers.Default) { - model.initState() - } - RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys)) + val model = createSwitchModel(applicationInfo) + val restrictions = Restrictions(userId, switchRestrictionKeys) + RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory) } } @Composable -private fun createSwitchModel( - listModel: TogglePermissionAppListModel, - packageName: String, - userId: Int, -): TogglePermissionSwitchModel? { - val record = remember { - PackageManagers.getApplicationInfoAsUser(packageName, userId)?.let { app -> - listModel.transformItem(app) - } - } ?: return null - +private fun TogglePermissionAppListModel.createSwitchModel( + app: ApplicationInfo, +): TogglePermissionSwitchModel { val context = LocalContext.current - val isAllowed = listModel.isAllowed(record) - return remember { - TogglePermissionSwitchModel(context, listModel, record, isAllowed) - } + val record = remember(app) { transformItem(app) } + val isAllowed = isAllowed(record) + return remember(record) { TogglePermissionSwitchModel(context, this, record, isAllowed) } + .also { model -> LaunchedEffect(model, Dispatchers.IO) { model.initState() } } } private class TogglePermissionSwitchModel( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt index a003da81e06d2..b08b6dfa67a6d 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -38,19 +38,14 @@ import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode import com.android.settingslib.spaprivileged.model.enterprise.Restrictions -import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl @Composable -fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) { - RestrictedSwitchPreferenceImpl(model, restrictions, ::RestrictionsProviderImpl) -} - -@Composable -internal fun RestrictedSwitchPreferenceImpl( +fun RestrictedSwitchPreference( model: SwitchPreferenceModel, restrictions: Restrictions, - restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider, + restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl, ) { if (restrictions.keys.isEmpty()) { SwitchPreference(model) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt new file mode 100644 index 0000000000000..ecad08a0aca24 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt @@ -0,0 +1,153 @@ +/* + * 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.template.app + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spaprivileged.model.app.IPackageManagers +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider +import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel +import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class TogglePermissionAppInfoPageTest { + @get:Rule + val composeTestRule = createComposeRule() + + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var packageManagers: IPackageManagers + + private val fakeRestrictionsProvider = FakeRestrictionsProvider() + + private val appListTemplate = + TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider)) + + private val appInfoPageProvider = TogglePermissionAppInfoPageProvider(appListTemplate) + + @Before + fun setUp() { + fakeRestrictionsProvider.restrictedMode = NoRestricted + whenever(packageManagers.getPackageInfoAsUser(PACKAGE_NAME, USER_ID)) + .thenReturn(PACKAGE_INFO) + } + + @Test + fun buildEntry() { + val entryList = appInfoPageProvider.buildEntry(null) + + assertThat(entryList).hasSize(1) + assertThat(entryList[0].displayName).isEqualTo("AllowControl") + } + + @Test + fun title_isDisplayed() { + val listModel = TestTogglePermissionAppListModel() + + setTogglePermissionAppInfoPage(listModel) + + composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId)) + .assertIsDisplayed() + } + + @Test + fun whenAllowed_switchIsOn() { + val listModel = TestTogglePermissionAppListModel(isAllowed = true) + + setTogglePermissionAppInfoPage(listModel) + + composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)) + .assertIsOn() + } + + @Test + fun whenNotAllowed_switchIsOff() { + val listModel = TestTogglePermissionAppListModel(isAllowed = false) + + setTogglePermissionAppInfoPage(listModel) + + composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)) + .assertIsOff() + } + + @Test + fun whenNotChangeable_switchNotEnabled() { + val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false) + + setTogglePermissionAppInfoPage(listModel) + + composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)) + .assertIsDisplayed() + .assertIsNotEnabled() + } + + @Test + fun footer_isDisplayed() { + val listModel = TestTogglePermissionAppListModel() + + setTogglePermissionAppInfoPage(listModel) + + composeTestRule.onNodeWithText(context.getString(listModel.footerResId)) + .assertIsDisplayed() + } + + private fun setTogglePermissionAppInfoPage(listModel: TestTogglePermissionAppListModel) { + composeTestRule.setContent { + listModel.TogglePermissionAppInfoPage( + packageName = PACKAGE_NAME, + userId = USER_ID, + packageManagers = packageManagers, + restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider }, + ) + } + } + + private companion object { + const val USER_ID = 0 + const val PACKAGE_NAME = "package.name" + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + val PACKAGE_INFO = PackageInfo().apply { + packageName = PACKAGE_NAME + applicationInfo = APP + } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt index 2d169e0357fe7..1818f2d92f9cd 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spaprivileged.test.R -import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel +import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -70,8 +70,3 @@ class TogglePermissionAppListTest { assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue() } } - -private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider { - override val permissionType = "test.PERMISSION" - override fun createModel(context: Context) = TestTogglePermissionAppListModel() -} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt index 7f57025c5fe5c..a13c4835b5d5b 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt @@ -142,7 +142,7 @@ class RestrictedSwitchPreferenceTest { private fun setContent(restrictions: Restrictions) { composeTestRule.setContent { - RestrictedSwitchPreferenceImpl(switchPreferenceModel, restrictions) { _, _ -> + RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ -> fakeRestrictionsProvider } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt index 64bc11f0bac57..b13fbb3e8e41a 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt @@ -23,7 +23,10 @@ import com.android.settingslib.spaprivileged.test.R import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel import kotlinx.coroutines.flow.Flow -class TestTogglePermissionAppListModel : TogglePermissionAppListModel { +class TestTogglePermissionAppListModel( + private val isAllowed: Boolean? = null, + private val isChangeable: Boolean = false, +) : TogglePermissionAppListModel { override val pageTitleResId = R.string.test_permission_title override val switchTitleResId = R.string.test_permission_switch_title override val footerResId = R.string.test_permission_footer @@ -34,9 +37,9 @@ class TestTogglePermissionAppListModel : TogglePermissionAppListModel