Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt 0 → 100644 +62 −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.Context import android.content.pm.ApplicationInfo import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach interface IAppOpsPermissionController { val isAllowedFlow: Flow<Boolean> fun setAllowed(allowed: Boolean) } class AppOpsPermissionController( context: Context, private val app: ApplicationInfo, appOps: AppOps, private val permission: String, private val packageManagers: IPackageManagers = PackageManagers, private val appOpsController: IAppOpsController = AppOpsController(context, app, appOps), ) : IAppOpsPermissionController { override val isAllowedFlow: Flow<Boolean> = appOpsController.modeFlow.map { mode -> when (mode) { AppOpsManager.MODE_ALLOWED -> true AppOpsManager.MODE_DEFAULT -> { with(packageManagers) { app.hasGrantPermission(permission) } } else -> false } }.conflate().onEach { Log.d(TAG, "isAllowed: $it") }.flowOn(Dispatchers.Default) override fun setAllowed(allowed: Boolean) { appOpsController.setAllowed(allowed) } private companion object { private const val TAG = "AppOpsPermissionControl" } } packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +21 −44 Original line number Diff line number Diff line Loading @@ -20,13 +20,14 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.AppOps import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import kotlinx.coroutines.flow.Flow Loading @@ -37,7 +38,7 @@ data class AppOpPermissionRecord( override val app: ApplicationInfo, val hasRequestBroaderPermission: Boolean, val hasRequestPermission: Boolean, var appOpsController: IAppOpsController, var appOpsPermissionController: IAppOpsPermissionController, ) : AppRecord abstract class AppOpPermissionListModel( Loading Loading @@ -70,8 +71,8 @@ abstract class AppOpPermissionListModel( private val notChangeablePackages = setOf("android", "com.android.systemui", context.packageName) private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(context, app, appOps) private fun createAppOpsPermissionController(app: ApplicationInfo) = AppOpsPermissionController(context, app, appOps, permission) private fun createRecord( app: ApplicationInfo, Loading @@ -84,7 +85,7 @@ abstract class AppOpPermissionListModel( app.hasRequestPermission(it) } ?: false, hasRequestPermission = hasRequestPermission, appOpsController = createAppOpsController(app), appOpsPermissionController = createAppOpsPermissionController(app), ) } Loading Loading @@ -117,51 +118,27 @@ abstract class AppOpPermissionListModel( override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) = recordListFlow.filterItem(::isChangeable) @Composable override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? = isAllowed( record = record, appOpsController = record.appOpsController, permission = permission, packageManagers = packageManagers, ) override fun isChangeable(record: AppOpPermissionRecord) = record.hasRequestPermission && !record.hasRequestBroaderPermission && record.app.packageName !in notChangeablePackages override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { record.appOpsController.setAllowed(newAllowed) } } /** * Defining the default behavior as permissible as long as the package requested this permission * (This means pre-M gets approval during install time; M apps gets approval during runtime). */ @Composable internal fun isAllowed( record: AppOpPermissionRecord, appOpsController: IAppOpsController, permission: String, packageManagers: IPackageManagers = PackageManagers, ): () -> Boolean? { override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? { if (record.hasRequestBroaderPermission) { // Broader permission trumps the specific permission. return { true } } val mode = appOpsController.modeFlow.collectAsStateWithLifecycle(initialValue = null) return { when (mode.value) { null -> null AppOpsManager.MODE_ALLOWED -> true AppOpsManager.MODE_DEFAULT -> { with(packageManagers) { record.app.hasGrantPermission(permission) } val isAllowed by record.appOpsPermissionController.isAllowedFlow .collectAsStateWithLifecycle(initialValue = null) return { isAllowed } } else -> false } override fun isChangeable(record: AppOpPermissionRecord) = record.hasRequestPermission && !record.hasRequestBroaderPermission && record.app.packageName !in notChangeablePackages override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { record.appOpsPermissionController.setAllowed(newAllowed) } } packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt 0 → 100644 +167 −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.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager 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.framework.common.appOpsManager import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.stub import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class AppOpsPermissionControllerTest { private val appOpsManager = mock<AppOpsManager>() private val packageManager = mock<PackageManager>() private val packageManagers = mock<IPackageManagers>() private val appOpsController = mock<IAppOpsController>() private val context: Context = spy(ApplicationProvider.getApplicationContext()) { on { appOpsManager } doReturn appOpsManager on { packageManager } doReturn packageManager } @Test fun isAllowedFlow_appOpsAllowed_returnTrue() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ALLOWED) } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isTrue() } @Test fun isAllowedFlow_appOpsDefaultAndPermissionGranted_returnTrue() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT) } packageManagers.stub { on { APP.hasGrantPermission(PERMISSION) } doReturn true } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, packageManagers = packageManagers, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isTrue() } @Test fun isAllowedFlow_appOpsDefaultAndPermissionNotGranted_returnFalse() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT) } packageManagers.stub { on { APP.hasGrantPermission(PERMISSION) } doReturn false } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, packageManagers = packageManagers, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isFalse() } @Test fun isAllowedFlow_appOpsError_returnFalse() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ERRORED) } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isFalse() } @Test fun setAllowed_notSetModeByUid() { val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP, setModeByUid = false), permission = PERMISSION, ) controller.setAllowed(true) verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, AppOpsManager.MODE_ALLOWED) } @Test fun setAllowed_setModeByUid() { val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP, setModeByUid = true), permission = PERMISSION, ) controller.setAllowed(true) verify(appOpsManager).setUidMode(OP, APP.uid, AppOpsManager.MODE_ALLOWED) } private companion object { const val OP = 1 const val PERMISSION = "Permission" val APP = ApplicationInfo().apply { packageName = "package.name" uid = 123 } } } packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt +14 −48 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spaprivileged.framework.common.appOpsManager import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.test.R import com.google.common.truth.Truth.assertThat Loading Loading @@ -119,7 +119,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = false, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record))) Loading @@ -135,7 +135,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED), appOpsPermissionController = FakeAppOpsPermissionController(true), ) val isAllowed = getIsAllowed(record) Loading @@ -143,38 +143,6 @@ class AppOpPermissionAppListTest { assertThat(isAllowed).isTrue() } @Test fun isAllowed_defaultAndHasGrantPermission() { with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) } val record = AppOpPermissionRecord( app = APP, hasRequestBroaderPermission = false, 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, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), ) val isAllowed = getIsAllowed(record) assertThat(isAllowed).isFalse() } @Test fun isAllowed_broaderPermissionTrumps() { listModel.broaderPermission = BROADER_PERMISSION Loading @@ -187,7 +155,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = true, hasRequestPermission = false, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isAllowed = getIsAllowed(record) Loading @@ -202,7 +170,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isAllowed = getIsAllowed(record) Loading @@ -217,7 +185,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = false, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -232,7 +200,7 @@ class AppOpPermissionAppListTest { app = NOT_CHANGEABLE_APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -247,7 +215,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -263,7 +231,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = true, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -273,18 +241,18 @@ class AppOpPermissionAppListTest { @Test fun setAllowed() { val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val appOpsPermissionController = FakeAppOpsPermissionController(false) val record = AppOpPermissionRecord( app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = appOpsController, appOpsPermissionController = appOpsPermissionController, ) listModel.setAllowed(record = record, newAllowed = true) assertThat(appOpsController.setAllowedCalledWith).isTrue() assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue() } private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? { Loading Loading @@ -314,14 +282,12 @@ class AppOpPermissionAppListTest { } } private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController { private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController { var setAllowedCalledWith: Boolean? = null override val modeFlow = flowOf(fakeMode) override val isAllowedFlow = flowOf(allowed) override fun setAllowed(allowed: Boolean) { setAllowedCalledWith = allowed } override fun getMode() = fakeMode } Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt 0 → 100644 +62 −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.Context import android.content.pm.ApplicationInfo import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach interface IAppOpsPermissionController { val isAllowedFlow: Flow<Boolean> fun setAllowed(allowed: Boolean) } class AppOpsPermissionController( context: Context, private val app: ApplicationInfo, appOps: AppOps, private val permission: String, private val packageManagers: IPackageManagers = PackageManagers, private val appOpsController: IAppOpsController = AppOpsController(context, app, appOps), ) : IAppOpsPermissionController { override val isAllowedFlow: Flow<Boolean> = appOpsController.modeFlow.map { mode -> when (mode) { AppOpsManager.MODE_ALLOWED -> true AppOpsManager.MODE_DEFAULT -> { with(packageManagers) { app.hasGrantPermission(permission) } } else -> false } }.conflate().onEach { Log.d(TAG, "isAllowed: $it") }.flowOn(Dispatchers.Default) override fun setAllowed(allowed: Boolean) { appOpsController.setAllowed(allowed) } private companion object { private const val TAG = "AppOpsPermissionControl" } }
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +21 −44 Original line number Diff line number Diff line Loading @@ -20,13 +20,14 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.AppOps import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import kotlinx.coroutines.flow.Flow Loading @@ -37,7 +38,7 @@ data class AppOpPermissionRecord( override val app: ApplicationInfo, val hasRequestBroaderPermission: Boolean, val hasRequestPermission: Boolean, var appOpsController: IAppOpsController, var appOpsPermissionController: IAppOpsPermissionController, ) : AppRecord abstract class AppOpPermissionListModel( Loading Loading @@ -70,8 +71,8 @@ abstract class AppOpPermissionListModel( private val notChangeablePackages = setOf("android", "com.android.systemui", context.packageName) private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(context, app, appOps) private fun createAppOpsPermissionController(app: ApplicationInfo) = AppOpsPermissionController(context, app, appOps, permission) private fun createRecord( app: ApplicationInfo, Loading @@ -84,7 +85,7 @@ abstract class AppOpPermissionListModel( app.hasRequestPermission(it) } ?: false, hasRequestPermission = hasRequestPermission, appOpsController = createAppOpsController(app), appOpsPermissionController = createAppOpsPermissionController(app), ) } Loading Loading @@ -117,51 +118,27 @@ abstract class AppOpPermissionListModel( override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) = recordListFlow.filterItem(::isChangeable) @Composable override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? = isAllowed( record = record, appOpsController = record.appOpsController, permission = permission, packageManagers = packageManagers, ) override fun isChangeable(record: AppOpPermissionRecord) = record.hasRequestPermission && !record.hasRequestBroaderPermission && record.app.packageName !in notChangeablePackages override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { record.appOpsController.setAllowed(newAllowed) } } /** * Defining the default behavior as permissible as long as the package requested this permission * (This means pre-M gets approval during install time; M apps gets approval during runtime). */ @Composable internal fun isAllowed( record: AppOpPermissionRecord, appOpsController: IAppOpsController, permission: String, packageManagers: IPackageManagers = PackageManagers, ): () -> Boolean? { override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? { if (record.hasRequestBroaderPermission) { // Broader permission trumps the specific permission. return { true } } val mode = appOpsController.modeFlow.collectAsStateWithLifecycle(initialValue = null) return { when (mode.value) { null -> null AppOpsManager.MODE_ALLOWED -> true AppOpsManager.MODE_DEFAULT -> { with(packageManagers) { record.app.hasGrantPermission(permission) } val isAllowed by record.appOpsPermissionController.isAllowedFlow .collectAsStateWithLifecycle(initialValue = null) return { isAllowed } } else -> false } override fun isChangeable(record: AppOpPermissionRecord) = record.hasRequestPermission && !record.hasRequestBroaderPermission && record.app.packageName !in notChangeablePackages override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { record.appOpsPermissionController.setAllowed(newAllowed) } }
packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt 0 → 100644 +167 −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.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager 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.framework.common.appOpsManager import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.stub import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class AppOpsPermissionControllerTest { private val appOpsManager = mock<AppOpsManager>() private val packageManager = mock<PackageManager>() private val packageManagers = mock<IPackageManagers>() private val appOpsController = mock<IAppOpsController>() private val context: Context = spy(ApplicationProvider.getApplicationContext()) { on { appOpsManager } doReturn appOpsManager on { packageManager } doReturn packageManager } @Test fun isAllowedFlow_appOpsAllowed_returnTrue() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ALLOWED) } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isTrue() } @Test fun isAllowedFlow_appOpsDefaultAndPermissionGranted_returnTrue() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT) } packageManagers.stub { on { APP.hasGrantPermission(PERMISSION) } doReturn true } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, packageManagers = packageManagers, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isTrue() } @Test fun isAllowedFlow_appOpsDefaultAndPermissionNotGranted_returnFalse() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT) } packageManagers.stub { on { APP.hasGrantPermission(PERMISSION) } doReturn false } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, packageManagers = packageManagers, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isFalse() } @Test fun isAllowedFlow_appOpsError_returnFalse() = runBlocking { appOpsController.stub { on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ERRORED) } val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP), permission = PERMISSION, appOpsController = appOpsController, ) val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() assertThat(isAllowed).isFalse() } @Test fun setAllowed_notSetModeByUid() { val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP, setModeByUid = false), permission = PERMISSION, ) controller.setAllowed(true) verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, AppOpsManager.MODE_ALLOWED) } @Test fun setAllowed_setModeByUid() { val controller = AppOpsPermissionController( context = context, app = APP, appOps = AppOps(op = OP, setModeByUid = true), permission = PERMISSION, ) controller.setAllowed(true) verify(appOpsManager).setUidMode(OP, APP.uid, AppOpsManager.MODE_ALLOWED) } private companion object { const val OP = 1 const val PERMISSION = "Permission" val APP = ApplicationInfo().apply { packageName = "package.name" uid = 123 } } }
packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt +14 −48 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spaprivileged.framework.common.appOpsManager import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.test.R import com.google.common.truth.Truth.assertThat Loading Loading @@ -119,7 +119,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = false, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record))) Loading @@ -135,7 +135,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED), appOpsPermissionController = FakeAppOpsPermissionController(true), ) val isAllowed = getIsAllowed(record) Loading @@ -143,38 +143,6 @@ class AppOpPermissionAppListTest { assertThat(isAllowed).isTrue() } @Test fun isAllowed_defaultAndHasGrantPermission() { with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) } val record = AppOpPermissionRecord( app = APP, hasRequestBroaderPermission = false, 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, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), ) val isAllowed = getIsAllowed(record) assertThat(isAllowed).isFalse() } @Test fun isAllowed_broaderPermissionTrumps() { listModel.broaderPermission = BROADER_PERMISSION Loading @@ -187,7 +155,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = true, hasRequestPermission = false, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isAllowed = getIsAllowed(record) Loading @@ -202,7 +170,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isAllowed = getIsAllowed(record) Loading @@ -217,7 +185,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = false, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -232,7 +200,7 @@ class AppOpPermissionAppListTest { app = NOT_CHANGEABLE_APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -247,7 +215,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -263,7 +231,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = true, hasRequestPermission = true, appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) Loading @@ -273,18 +241,18 @@ class AppOpPermissionAppListTest { @Test fun setAllowed() { val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val appOpsPermissionController = FakeAppOpsPermissionController(false) val record = AppOpPermissionRecord( app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, appOpsController = appOpsController, appOpsPermissionController = appOpsPermissionController, ) listModel.setAllowed(record = record, newAllowed = true) assertThat(appOpsController.setAllowedCalledWith).isTrue() assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue() } private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? { Loading Loading @@ -314,14 +282,12 @@ class AppOpPermissionAppListTest { } } private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController { private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController { var setAllowedCalledWith: Boolean? = null override val modeFlow = flowOf(fakeMode) override val isAllowedFlow = flowOf(allowed) override fun setAllowed(allowed: Boolean) { setAllowedCalledWith = allowed } override fun getMode() = fakeMode }