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

Commit dcd0e12f authored by Nate Myren's avatar Nate Myren
Browse files

Add AutoRevoke UI

Add a PermissionController UI for the AutoRevoke feature
Test: none
Bug: 151252163

Change-Id: I98247633a23321b3a21b97c10f07512c89e72be5
parent 30c99155
Loading
Loading
Loading
Loading
+0 −3
Original line number Original line Diff line number Diff line
@@ -101,7 +101,6 @@
            app:popEnterAnim="@anim/activity_open_enter"/>
            app:popEnterAnim="@anim/activity_open_enter"/>
    </fragment>
    </fragment>



    <fragment
    <fragment
        android:id="@+id/all_app_permissions"
        android:id="@+id/all_app_permissions"
        android:name="com.android.permissioncontroller.permission.ui.handheld.AllAppPermissionsFragment"
        android:name="com.android.permissioncontroller.permission.ui.handheld.AllAppPermissionsFragment"
@@ -132,7 +131,5 @@
            app:enterAnim="@anim/activity_open_enter"
            app:enterAnim="@anim/activity_open_enter"
            app:popExitAnim="@anim/activity_close_exit"
            app:popExitAnim="@anim/activity_close_exit"
            app:popEnterAnim="@anim/activity_open_enter"/>
            app:popEnterAnim="@anim/activity_open_enter"/>

    </fragment>
    </fragment>

</navigation>
</navigation>
 No newline at end of file
+18 −0
Original line number Original line Diff line number Diff line
@@ -307,6 +307,24 @@
    <!-- Text for linking to the page that shows the apps with a given permission [CHAR LIMIT=none] -->
    <!-- Text for linking to the page that shows the apps with a given permission [CHAR LIMIT=none] -->
    <string name="app_permission_footer_permission_apps_link">See all apps with this permission</string>
    <string name="app_permission_footer_permission_apps_link">See all apps with this permission</string>


    <!-- Label for the auto revoke switch [CHAR LIMIT=60] -->
    <string name="auto_revoke_label">Auto revoke permissions</string>

    <!-- Summary for stating that auto revoke is disabled for this app[CHAR LIMIT=none] -->
    <string name="auto_revoke_summary">To protect your data, permissions for this app will be removed if the app isn\u2019t used for a few months.</string>

    <!-- Summary for stating that no permission groups that are granted and auto revocable[CHAR LIMIT=none] -->
    <string name="auto_revocable_permissions_none">No auto revocable permissions are currently granted</string>

    <!-- Summary for stating that one permission will be auto revoked[CHAR LIMIT=none] -->
    <string name="auto_revocable_permissions_one"><xliff:g id="perm" example="location">%1$s</xliff:g> permission will be removed.</string>

    <!-- Summary for stating that two permissions will be auto revoked[CHAR LIMIT=none] -->
    <string name="auto_revocable_permissions_two"><xliff:g id="perm" example="location">%1$s</xliff:g> and <xliff:g id="perm" example="contacts">%2$s</xliff:g> permissions will be removed.</string>

    <!-- Summary for stating that more than two permissions will be auto revoked[CHAR LIMIT=none] -->
    <string name="auto_revocable_permissions_many">Permissions that will be removed: <xliff:g id="perms" example="location, contacts, and phone">%1$s</xliff:g>.</string>

    <!-- Label for showing a permission group's description in the header of the list of apps that have that permission [CHAR LIMIT=none] -->
    <!-- Label for showing a permission group's description in the header of the list of apps that have that permission [CHAR LIMIT=none] -->
    <string name="permission_description_summary_generic">Apps with this permission can <xliff:g id="description" example="record audio">%1$s</xliff:g></string>
    <string name="permission_description_summary_generic">Apps with this permission can <xliff:g id="description" example="record audio">%1$s</xliff:g></string>


+160 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 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.permissioncontroller.permission.data

import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
import android.app.Application
import android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
import android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
import android.os.UserHandle
import android.provider.DeviceConfig
import androidx.lifecycle.Observer
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
import com.android.permissioncontroller.permission.model.livedatatypes.AutoRevokeState
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.Utils
import kotlinx.coroutines.Job
import java.util.concurrent.TimeUnit

/**
 * A LiveData which tracks the AutoRevoke state for one user package.
 *
 * @param app The current application
 * @param packageName The package name whose state we want
 * @param user The user for whom we want the package
 */
class AutoRevokeStateLiveData private constructor(
    app: Application,
    private val packageName: String,
    private val user: UserHandle
) : SmartAsyncMediatorLiveData<AutoRevokeState>(), AppOpsManager.OnOpChangedListener {

    private val packagePermsLiveData =
        PackagePermissionsLiveData[packageName, user]
    private val packageLiveData = LightPackageInfoLiveData[packageName, user]
    private val permStateLiveDatas = mutableMapOf<String, PermStateLiveData>()
    private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!

    init {
        addSource(packagePermsLiveData) {
            updateIfActive()
        }
        addSource(packageLiveData) {
            updateIfActive()
        }
    }

    override suspend fun loadDataAndPostValue(job: Job) {
        val uid = packageLiveData.value?.uid
        if (uid == null && packageLiveData.isInitialized) {
            postValue(null)
            return
        } else if (uid == null) {
            return
        }

        val groups = packagePermsLiveData.value?.keys?.filter { it != NON_RUNTIME_NORMAL_PERMS }
        if (groups == null && packagePermsLiveData.isInitialized) {
            postValue(null)
            return
        } else if (groups == null) {
            return
        }

        addAndRemovePermStateLiveDatas(groups)

        if (!permStateLiveDatas.all { it.value.isInitialized }) {
            return
        }

        val revocable = appOpsManager.unsafeCheckOpNoThrow(
            OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName) == MODE_ALLOWED
        val autoRevokeState = mutableListOf<String>()
        permStateLiveDatas.forEach { (groupName, liveData) ->
            val default = liveData.value?.any { (_, permState) ->
                permState.permFlags and (FLAG_PERMISSION_GRANTED_BY_DEFAULT or
                    FLAG_PERMISSION_GRANTED_BY_ROLE) != 0
            } ?: false
            if (!default) {
                autoRevokeState.add(groupName)
            }
        }

        postValue(AutoRevokeState(isAutoRevokeEnabledGlobal(), revocable, autoRevokeState))
    }

    private fun isAutoRevokeEnabledGlobal(): Boolean {
        val unusedThreshold = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
                Utils.PROPERTY_AUTO_REVOKE_UNUSED_THRESHOLD_MILLIS, TimeUnit.DAYS.toMillis(90))
        val checkFrequency = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
            Utils.PROPERTY_AUTO_REVOKE_CHECK_FREQUENCY_MILLIS, TimeUnit.DAYS.toMillis(1))
        return unusedThreshold > 0 && checkFrequency > 0
    }

    private fun addAndRemovePermStateLiveDatas(groupNames: List<String>) {
        val (toAdd, toRemove) = KotlinUtils.getMapAndListDifferences(groupNames,
            permStateLiveDatas)

        for (groupToAdd in toAdd) {
            val permStateLiveData =
                PermStateLiveData[packageName, groupToAdd, user]
            permStateLiveDatas[groupToAdd] = permStateLiveData
        }

        for (groupToAdd in toAdd) {
            postAddSource(permStateLiveDatas[groupToAdd]!!, Observer {
                updateIfActive()
            })
        }

        for (groupToRemove in toRemove) {
            postRemoveSource(permStateLiveDatas[groupToRemove]!!)
            permStateLiveDatas.remove(groupToRemove)
        }
    }

    override fun onOpChanged(op: String?, packageName: String?) {
        if (op == OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED && packageName == packageName) {
            updateIfActive()
        }
    }

    override fun onActive() {
        super.onActive()
        appOpsManager.startWatchingMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageName, this)
        updateIfActive()
    }

    override fun onInactive() {
        super.onInactive()
        appOpsManager.stopWatchingMode(this)
    }
    /**
     * Repository for AutoRevokeStateLiveDatas.
     * <p> Key value is a pair of string package name and UserHandle, value is its corresponding
     * LiveData.
     */
    companion object : DataRepository<Pair<String, UserHandle>, AutoRevokeStateLiveData>() {
        override fun newValue(key: Pair<String, UserHandle>): AutoRevokeStateLiveData {
            return AutoRevokeStateLiveData(PermissionControllerApplication.get(),
                key.first, key.second)
        }
    }
}
+11 −0
Original line number Original line Diff line number Diff line
@@ -29,6 +29,9 @@ import androidx.lifecycle.Observer
import com.android.permissioncontroller.permission.utils.ensureMainThread
import com.android.permissioncontroller.permission.utils.ensureMainThread
import com.android.permissioncontroller.permission.utils.getInitializedValue
import com.android.permissioncontroller.permission.utils.getInitializedValue
import com.android.permissioncontroller.permission.utils.shortStackTrace
import com.android.permissioncontroller.permission.utils.shortStackTrace
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch


/**
/**
 * A MediatorLiveData which tracks how long it has been inactive, compares new values before setting
 * A MediatorLiveData which tracks how long it has been inactive, compares new values before setting
@@ -161,6 +164,14 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
        super.removeSource(toRemote)
        super.removeSource(toRemote)
    }
    }


    fun <S : Any?> postAddSource(source: LiveData<S>, onChanged: Observer<in S>) {
        GlobalScope.launch(Main) { addSource(source, onChanged) }
    }

    fun <S : Any?> postRemoveSource(toRemote: LiveData<S>) {
        GlobalScope.launch(Main) { removeSource(toRemote) }
    }

    private fun <S : Any?> removeChild(liveData: LiveData<S>) {
    private fun <S : Any?> removeChild(liveData: LiveData<S>) {
        children.removeIf { it.first == liveData }
        children.removeIf { it.first == liveData }
    }
    }
+1 −1
Original line number Original line Diff line number Diff line
@@ -29,7 +29,7 @@ data class AppPermGroupUiInfo(
    val permGrantState: PermGrantState,
    val permGrantState: PermGrantState,
    val isSystem: Boolean
    val isSystem: Boolean
) {
) {
    enum class PermGrantState(val grantState: Int) {
    enum class PermGrantState(private val grantState: Int) {
        PERMS_DENIED(0),
        PERMS_DENIED(0),
        PERMS_ALLOWED(1),
        PERMS_ALLOWED(1),
        PERMS_ALLOWED_FOREGROUND_ONLY(2),
        PERMS_ALLOWED_FOREGROUND_ONLY(2),
Loading