Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fcbcbddb authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Add AppOpPermissionAppList for SpaPrivilegedLib"

parents e5038d35 572f75db
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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",
+7 −4
Original line number Diff line number Diff line
@@ -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() {
+32 −11
Original line number Diff line number Diff line
@@ -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
}
+2 −1
Original line number Diff line number Diff line
@@ -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)
        }
+101 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}