summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt43
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt101
5 files changed, 143 insertions, 16 deletions
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index a4928e6608ab..6871f2154bd6 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -27,6 +27,7 @@ android_library {
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
"androidx.compose.runtime_runtime",
+ "androidx.compose.runtime_runtime-livedata",
"androidx.compose.ui_ui-tooling-preview",
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
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 8876f66bcafe..93ba4f7824b7 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
@@ -24,6 +24,7 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
class AppOpsController(
context: Context,
@@ -32,21 +33,23 @@ class AppOpsController(
) {
private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+ val mode: LiveData<Int>
+ get() = _mode
val isAllowed: LiveData<Boolean>
- get() = _isAllowed
+ get() = Transformations.map(_mode) { it == MODE_ALLOWED }
fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
appOpsManager.setMode(op, app.uid, app.packageName, mode)
- _isAllowed.postValue(allowed)
+ _mode.postValue(mode)
}
@Mode
fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
- private val _isAllowed = object : MutableLiveData<Boolean>() {
+ private val _mode = object : MutableLiveData<Int>() {
override fun onActive() {
- postValue(getMode() == MODE_ALLOWED)
+ postValue(getMode())
}
override fun onInactive() {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index e521edd6b845..ba8af5400e18 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -16,31 +16,52 @@
package com.android.settingslib.spaprivileged.model.app
+import android.app.AppGlobals
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
+import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
import android.content.pm.PackageManager
import android.util.Log
+import com.android.settingslib.spa.framework.util.asyncFilter
private const val TAG = "PackageManagers"
object PackageManagers {
- fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
- PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
+ private val iPackageManager by lazy { AppGlobals.getPackageManager() }
+
+ fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
+ getPackageInfoAsUser(packageName, 0, userId)
fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
- fun hasRequestPermission(app: ApplicationInfo, permission: String): Boolean {
- val packageInfo = try {
- PackageManager.getPackageInfoAsUserCached(
- app.packageName, PackageManager.GET_PERMISSIONS.toLong(), app.userId
- )
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
- return false
- }
+ fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
+ val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
return packageInfo?.requestedPermissions?.let {
permission in it
} ?: false
}
+
+ fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
+ val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
+ ?: return false
+ val index = packageInfo.requestedPermissions.indexOf(permission)
+ return index >= 0 &&
+ packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
+ }
+
+ suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
+ iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
+ iPackageManager.isPackageAvailable(it, userId)
+ }.toSet()
+
+ private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+ try {
+ PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
+ null
+ }
+
+ private fun Int.hasFlag(flag: Int) = (this and flag) > 0
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index 99deb707a351..f51d2dbfdeef 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -49,7 +49,8 @@ fun AppInfo(packageName: String, userId: Int) {
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
+ val packageInfo =
+ remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } ?: return
Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
}
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
new file mode 100644
index 000000000000..c6f41d3bd3e2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+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.spaprivileged.model.app.AppOpsController
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+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
+
+data class AppOpPermissionRecord(
+ override val app: ApplicationInfo,
+ val hasRequestPermission: Boolean,
+ var appOpsController: AppOpsController,
+) : AppRecord
+
+abstract class AppOpPermissionListModel(private val context: Context) :
+ TogglePermissionAppListModel<AppOpPermissionRecord> {
+
+ abstract val appOp: Int
+ abstract val permission: String
+
+ private val notChangeablePackages =
+ setOf("android", "com.android.systemui", context.packageName)
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ userIdFlow.map { userId ->
+ PackageManagers.getAppOpPermissionPackages(userId, permission)
+ }.combine(appListFlow) { packageNames, appList ->
+ appList.map { app ->
+ AppOpPermissionRecord(
+ app = app,
+ hasRequestPermission = app.packageName in packageNames,
+ appOpsController = AppOpsController(context = context, app = app, op = appOp),
+ )
+ }
+ }
+
+ override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
+ app = app,
+ hasRequestPermission = app.hasRequestPermission(permission),
+ appOpsController = AppOpsController(context = context, app = app, op = appOp),
+ )
+
+ override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
+ recordListFlow.map { recordList ->
+ recordList.filter { it.hasRequestPermission }
+ }
+
+ /**
+ * 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): State<Boolean?> {
+ val mode = record.appOpsController.mode.observeAsState()
+ return remember {
+ derivedStateOf {
+ when (mode.value) {
+ null -> null
+ MODE_ALLOWED -> true
+ MODE_DEFAULT -> record.app.hasGrantPermission(permission)
+ else -> false
+ }
+ }
+ }
+ }
+
+ override fun isChangeable(record: AppOpPermissionRecord) =
+ record.hasRequestPermission && record.app.packageName !in notChangeablePackages
+
+ override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
+ record.appOpsController.setAllowed(newAllowed)
+ }
+}