summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chaohui Wang <chaohuiw@google.com> 2024-05-21 05:23:09 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-05-21 05:23:09 +0000
commitdcefab81bc74a772d9fcc28a10bfc38aaa566a56 (patch)
tree70469c82764936efa928c8676e95ed2e38748014
parent23b478138ea70086e452db07099dd04dd388d7e8 (diff)
parent632b6d1512e5f85fbf27c0faeca54916895c9598 (diff)
Merge "Create AppOpsPermissionController" into 24D1-dev
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt62
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt65
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt167
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt62
4 files changed, 264 insertions, 92 deletions
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt
new file mode 100644
index 000000000000..9350f98841a4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt
@@ -0,0 +1,62 @@
+/*
+ * 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"
+ }
+}
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 37b1d731d548..120b75ecb666 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
@@ -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
@@ -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(
@@ -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,
@@ -84,7 +85,7 @@ abstract class AppOpPermissionListModel(
app.hasRequestPermission(it)
} ?: false,
hasRequestPermission = hasRequestPermission,
- appOpsController = createAppOpsController(app),
+ appOpsPermissionController = createAppOpsPermissionController(app),
)
}
@@ -117,14 +118,20 @@ abstract class AppOpPermissionListModel(
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
recordListFlow.filterItem(::isChangeable)
+ /**
+ * 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
- override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? =
- isAllowed(
- record = record,
- appOpsController = record.appOpsController,
- permission = permission,
- packageManagers = packageManagers,
- )
+ override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? {
+ if (record.hasRequestBroaderPermission) {
+ // Broader permission trumps the specific permission.
+ return { true }
+ }
+ val isAllowed by record.appOpsPermissionController.isAllowedFlow
+ .collectAsStateWithLifecycle(initialValue = null)
+ return { isAllowed }
+ }
override fun isChangeable(record: AppOpPermissionRecord) =
record.hasRequestPermission &&
@@ -132,36 +139,6 @@ abstract class AppOpPermissionListModel(
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? {
- 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) }
- }
-
- else -> false
- }
+ record.appOpsPermissionController.setAllowed(newAllowed)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
new file mode 100644
index 000000000000..9f80b92548d2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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
+ }
+ }
+}
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
index 07ccdd5a3695..9d12fc7611cb 100644
--- 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
@@ -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
@@ -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)))
@@ -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)
@@ -144,38 +144,6 @@ class AppOpPermissionAppListTest {
}
@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
with(packageManagers) {
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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? {
@@ -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
}