summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNate Myren <ntmyren@google.com>2020-07-29 15:28:10 -0700
committerNate Myren <ntmyren@google.com>2020-08-21 15:37:36 -0700
commite202d69020bcbfef3457de1a101a979535c40f56 (patch)
treea17882d78ad7762e4918aa8213b77b1b8e8160aa
parent5c2c1997d0fa96c6933b9b22702837e0877bffca (diff)
downloadPermission-e202d69020bcbfef3457de1a101a979535c40f56.tar.gz
Refactor GrantPermissionsActivity
Convert the GrantPermissionsActivity to the new LiveData and ViewModel architecture. Also changes the SmartUpdateMediatorLiveData stale behavior. Now, liveDatas will send the first non-stale update to all observers, and the observeStale method has been removed. Bug: 158311396 Test: atest CtsPermission3TestCases Change-Id: I78ae7112022f3b828e4464076a3612df10d2c221
-rw-r--r--PermissionController/res/values/themes.xml18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/CarrierPrivilegedStatusLiveData.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/ForegroundPermNamesLiveData.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt148
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java1232
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java322
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt313
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt989
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt131
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java46
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java60
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/permission/utils/GrantRevokeTests.kt6
18 files changed, 1769 insertions, 1544 deletions
diff --git a/PermissionController/res/values/themes.xml b/PermissionController/res/values/themes.xml
index 2a70fde21..4627fa4da 100644
--- a/PermissionController/res/values/themes.xml
+++ b/PermissionController/res/values/themes.xml
@@ -68,6 +68,24 @@
<item name="android:windowNoTitle">true</item>
</style>
+ <style name="Theme.PermissionGrantDialog.Transparent"
+ parent="android:Theme.DeviceDefault.Settings">
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+ <item name="android:navigationBarDividerColor">@null</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowDisablePreview">true</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowLightNavigationBar">false</item>
+ <item name="android:windowLightStatusBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+
+ </style>
+
<style name="Theme.PermissionGrantDialog"
parent="@android:style/Theme.DeviceDefault.Light.Dialog">
<item name="android:divider">@drawable/list_divider</item>
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
index a916cf8e8..0022317f2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
@@ -30,8 +30,8 @@ import com.android.permissioncontroller.PermissionControllerApplication
*
* @see AppOpsManager
*/
-//TODO eugenesusla: observe appops
-//TODO eugenesusla: use for external storage
+// TODO eugenesusla: observe appops
+// TODO eugenesusla: use for external storage
class AppOpLiveData private constructor(
private val app: Application,
private val packageName: String,
@@ -39,12 +39,17 @@ class AppOpLiveData private constructor(
private val uid: Int
) : SmartUpdateMediatorLiveData<Int>() {
- val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
+ private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
override fun onUpdate() {
value = appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName)
}
+ override fun onActive() {
+ super.onActive()
+ updateIfActive()
+ }
+
/**
* Repository for AppOpLiveData.
* <p> Key value is a triple of string package name, string appop, and
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/CarrierPrivilegedStatusLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/CarrierPrivilegedStatusLiveData.kt
index d87906217..04c43f5a7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/CarrierPrivilegedStatusLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/CarrierPrivilegedStatusLiveData.kt
@@ -37,6 +37,11 @@ class CarrierPrivilegedStatusLiveData private constructor(
value = telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
}
+ override fun onActive() {
+ super.onActive()
+ updateIfActive()
+ }
+
/**
* Repository for [CarrierPrivilegedStatusLiveData].
* <p> Key value is a package name, value is its corresponding LiveData of
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/ForegroundPermNamesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/ForegroundPermNamesLiveData.kt
index aa4a8b9f9..7120147dd 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/ForegroundPermNamesLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/ForegroundPermNamesLiveData.kt
@@ -23,7 +23,7 @@ import com.android.permissioncontroller.permission.utils.Utils
* installed, runtime permission in every platform permission group. This LiveData's value is
* static, since the background/foreground permission relationships are defined by the system.
*/
-object ForegroundPermNamesLiveData : SmartUpdateMediatorLiveData<Map<String, List<String>>>() {
+object ForegroundPermNamesLiveData : SmartUpdateMediatorLiveData<Map<String, List<String>>>(true) {
// Since the value will be static, initialize the value upon creating the LiveData.
init {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt
index 8ff5eaa33..5d94588fd 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt
@@ -190,11 +190,11 @@ class LightAppPermGroupLiveData private constructor(
}
override fun onInactive() {
- super.onInactive()
-
if (isSpecialLocation) {
LocationUtils.removeLocationListener(this)
}
+
+ super.onInactive()
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
index 84826c00a..956e373d1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
@@ -18,11 +18,6 @@ package com.android.permissioncontroller.permission.data
import android.util.Log
import androidx.annotation.MainThread
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.Lifecycle.State
-import androidx.lifecycle.Lifecycle.State.STARTED
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Observer
@@ -39,8 +34,8 @@ import kotlinx.coroutines.launch
* its value (avoiding unnecessary updates), and can calculate the set difference between a list
* and a map (used when determining whether or not to add a LiveData as a source).
*/
-abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
- DataRepository.InactiveTimekeeper {
+abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean = false)
+ : MediatorLiveData<T>(), DataRepository.InactiveTimekeeper {
companion object {
const val DEBUG_UPDATES = false
@@ -62,13 +57,8 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
var isStale = true
private set
- private val staleObservers = mutableListOf<Pair<LifecycleOwner, Observer<in T>>>()
-
private val sources = mutableListOf<SmartUpdateMediatorLiveData<*>>()
- private val children =
- mutableListOf<Triple<SmartUpdateMediatorLiveData<*>, Observer<in T>, Boolean>>()
-
private val stacktraceExceptionMessage = "Caller of coroutine"
@MainThread
@@ -77,31 +67,21 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
if (!isInitialized) {
isInitialized = true
- isStale = false
// If we have received an invalid value, and this is the first time we are set,
// notify observers.
if (newValue == null) {
+ isStale = false
super.setValue(newValue)
return
}
}
- if (valueNotEqual(super.getValue(), newValue)) {
- isStale = false
- super.setValue(newValue)
- } else if (isStale) {
- isStale = false
- // We are no longer stale- notify active stale observers we are up-to-date
- val liveObservers = staleObservers.filter { it.first.lifecycle.currentState >= STARTED }
- for ((_, observer) in liveObservers) {
- observer.onChanged(newValue)
- }
+ val wasStale = isStale
+ // If all sources are not stale, and we are active, we are not stale
+ isStale = sources.any { it.isStale } || !hasActiveObservers()
- for ((liveData, observer, shouldUpdate) in children.toList()) {
- if (liveData.hasActiveObservers() && shouldUpdate) {
- observer.onChanged(newValue)
- }
- }
+ if (valueNotEqual(super.getValue(), newValue) || (wasStale && !isStale)) {
+ super.setValue(newValue)
}
}
@@ -136,18 +116,6 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
return valOne != valTwo
}
- @MainThread
- fun observeStale(owner: LifecycleOwner, observer: Observer<in T>) {
- val oldStaleObserver = hasStaleObserver()
- staleObservers.add(owner to observer)
- if (owner == ForeverActiveLifecycle) {
- observeForever(observer)
- } else {
- observe(owner, observer)
- }
- updateSourceStaleObservers(oldStaleObserver, true)
- }
-
override fun <S : Any?> addSource(source: LiveData<S>, onChanged: Observer<in S>) {
addSourceWithError(source, onChanged)
}
@@ -165,8 +133,6 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
if (source in sources) {
return@launch
}
- source.addChild(this@SmartUpdateMediatorLiveData, onChanged,
- staleObservers.isNotEmpty() || children.any { it.third })
sources.add(source)
}
try {
@@ -180,7 +146,6 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
override fun <S : Any?> removeSource(toRemote: LiveData<S>) {
GlobalScope.launch(Main.immediate) {
if (toRemote is SmartUpdateMediatorLiveData) {
- toRemote.removeChild(this@SmartUpdateMediatorLiveData)
sources.remove(toRemote)
}
super.removeSource(toRemote)
@@ -240,81 +205,22 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
}
}
- @MainThread
- private fun <S : Any?> removeChild(liveData: LiveData<S>) {
- children.removeIf { it.first == liveData }
- }
-
- @MainThread
- private fun <S : Any?> addChild(
- liveData: SmartUpdateMediatorLiveData<S>,
- onChanged: Observer<in T>,
- sendStaleUpdates: Boolean
- ) {
- children.add(Triple(liveData, onChanged, sendStaleUpdates))
- }
-
- @MainThread
- private fun <S : Any?> updateShouldSendStaleUpdates(
- liveData: SmartUpdateMediatorLiveData<S>,
- sendStaleUpdates: Boolean
- ) {
- for ((idx, childTriple) in children.withIndex()) {
- if (childTriple.first == liveData) {
- children[idx] = Triple(liveData, childTriple.second, sendStaleUpdates)
- }
- }
- }
-
- @MainThread
- override fun removeObserver(observer: Observer<in T>) {
- val oldStaleObserver = hasStaleObserver()
- staleObservers.removeIf { it.second == observer }
- super.removeObserver(observer)
- updateSourceStaleObservers(oldStaleObserver, hasStaleObserver())
- }
-
- @MainThread
- override fun removeObservers(owner: LifecycleOwner) {
- val oldStaleObserver = hasStaleObserver()
- staleObservers.removeIf { it.first == owner }
- super.removeObservers(owner)
- updateSourceStaleObservers(oldStaleObserver, hasStaleObserver())
- }
-
- @MainThread
- override fun observeForever(observer: Observer<in T>) {
- super.observeForever(observer)
- }
-
- @MainThread
- private fun updateSourceStaleObservers(hadStaleObserver: Boolean, hasStaleObserver: Boolean) {
- if (hadStaleObserver == hasStaleObserver) {
- return
- }
- for (liveData in sources) {
- liveData.updateShouldSendStaleUpdates(this, hasStaleObserver)
- }
-
- // if all sources are not stale, and we just requested stale updates, and we are stale,
- // update our value
- if (sources.all { !it.isStale } && hasStaleObserver && isStale) {
- updateIfActive()
- }
- }
-
- private fun hasStaleObserver(): Boolean {
- return staleObservers.isNotEmpty() || children.any { it.third }
- }
-
override fun onActive() {
timeWentInactive = null
+ // If this is not an async livedata, and we have sources, and all sources are non-stale,
+ // force update our value
+ if (sources.isNotEmpty() && sources.all { !it.isStale } &&
+ this !is SmartAsyncMediatorLiveData<T>) {
+ updateIfActive()
+ }
super.onActive()
}
override fun onInactive() {
timeWentInactive = System.nanoTime()
- isStale = true
+ if (!isStaticVal) {
+ isStale = true
+ }
super.onInactive()
}
@@ -327,29 +233,11 @@ abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(),
suspend fun getInitializedValue(staleOk: Boolean = false, forceUpdate: Boolean = false): T {
return getInitializedValue(
observe = { observer ->
- observeStale(ForeverActiveLifecycle, observer)
+ observeForever(observer)
if (forceUpdate) {
updateIfActive()
}
},
isInitialized = { isInitialized && (staleOk || !isStale) })
}
-
- /**
- * A [Lifecycle]/[LifecycleOwner] that is permanently [State.STARTED]
- *
- * Passing this to [LiveData.observe] is essentially equivalent to using
- * [LiveData.observeForever], so you have to make sure you handle your own cleanup whenever
- * using this.
- */
- private object ForeverActiveLifecycle : Lifecycle(), LifecycleOwner {
-
- override fun getLifecycle(): Lifecycle = this
-
- override fun addObserver(observer: LifecycleObserver) {}
-
- override fun removeObserver(observer: LifecycleObserver) {}
-
- override fun getCurrentState(): State = State.STARTED
- }
} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt
index ef941054f..5a89be61b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt
@@ -16,6 +16,7 @@
package com.android.permissioncontroller.permission.model.livedatatypes
+import android.content.pm.PermissionInfo
import android.os.Build
import android.os.UserHandle
@@ -78,10 +79,10 @@ data class LightAppPermGroup(
}
val foreground = AppPermSubGroup(permissions.filter { it.key in foregroundPermNames },
- specialLocationGrant)
+ packageInfo, specialLocationGrant)
val background = AppPermSubGroup(permissions.filter { it.key in backgroundPermNames },
- specialLocationGrant)
+ packageInfo, specialLocationGrant)
/**
* Whether or not this App Permission Group has a permission which has a background mode
@@ -144,6 +145,7 @@ data class LightAppPermGroup(
*/
data class AppPermSubGroup internal constructor(
private val permissions: Map<String, LightPermission>,
+ private val packageInfo: LightPackageInfo,
private val specialLocationGrant: Boolean?
) {
/**
@@ -180,5 +182,12 @@ data class LightAppPermGroup(
* Whether any of this App Permission Subgroup's permissions are set by the role of this app
*/
val isGrantedByRole = permissions.any { it.value.isGrantedByRole }
+
+ private val hasPreRuntimePerm = permissions.any { (_, perm) ->
+ perm.permInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY == 0
+ }
+
+ val isGrantable = !packageInfo.isInstantApp &&
+ (packageInfo.targetSdkVersion >= Build.VERSION_CODES.M || hasPreRuntimePerm)
}
} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt
index ee4450201..a32855505 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt
@@ -92,11 +92,7 @@ class PermissionControllerServiceModel(private val service: PermissionController
}
}
- if (liveData is SmartUpdateMediatorLiveData<T>) {
- liveData.observeStale(service, observer)
- } else {
- liveData.observe(service, observer)
- }
+ liveData.observe(service, observer)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index f7ae2d244..289403685 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -16,24 +16,8 @@
package com.android.permissioncontroller.permission.ui;
-import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
@@ -41,67 +25,52 @@ import static com.android.permissioncontroller.permission.ui.GrantPermissionsVie
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_SETTINGS;
-import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
-import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED;
-import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT;
import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage;
-import android.Manifest;
-import android.app.Activity;
import android.app.KeyguardManager;
-import android.app.admin.DevicePolicyManager;
import android.content.Intent;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
-import android.os.Build;
import android.os.Bundle;
-import android.os.UserHandle;
-import android.permission.PermissionManager;
+import android.os.Process;
import android.text.Annotation;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ClickableSpan;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
+import androidx.fragment.app.FragmentActivity;
-import com.android.permissioncontroller.Constants;
import com.android.permissioncontroller.DeviceUtils;
-import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
-import com.android.permissioncontroller.permission.model.AppPermissions;
-import com.android.permissioncontroller.permission.model.Permission;
import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
-import com.android.permissioncontroller.permission.utils.ArrayUtils;
-import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
-import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Random;
-public class GrantPermissionsActivity extends Activity
+/**
+ * An activity which displays runtime permission prompts on behalf of an app.
+ */
+public class GrantPermissionsActivity extends FragmentActivity
implements GrantPermissionsViewHandler.ResultListener {
- private static final String LOG_TAG = "GrantPermissionsActivity";
+ private static final String LOG_TAG = "GrantPermissionsActivit";
- private static final String KEY_REQUEST_ID = GrantPermissionsActivity.class.getName()
+ private static final String KEY_SESSION_ID = GrantPermissionsActivity.class.getName()
+ "_REQUEST_ID";
public static final String ANNOTATION_ID = "link";
@@ -121,191 +90,43 @@ public class GrantPermissionsActivity extends Activity
private static final int APP_PERMISSION_REQUEST_CODE = 1;
/** Unique Id of a request */
- private long mRequestId;
+ private long mSessionId;
private String[] mRequestedPermissions;
private boolean[] mButtonVisibilities;
- private boolean mCouldHaveFgCapabilities;
-
- private ArrayMap<Pair<String, Boolean>, GroupState> mRequestGrantPermissionGroups =
- new ArrayMap<>();
- private ArraySet<String> mPermissionGroupsToSkip = new ArraySet<>();
- private Consumer<Intent> mActivityResultCallback;
-
+ private List<RequestInfo> mRequestInfos = new ArrayList<>();
private GrantPermissionsViewHandler mViewHandler;
- private AppPermissions mAppPermissions;
-
- boolean mResultSet;
-
- /**
- * Listens for changes to the permission of the app the permissions are currently getting
- * granted to. {@code null} when unregistered.
- */
- private @Nullable PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
-
- /**
- * Listens for changes to the app the permissions are currently getting granted to. {@code null}
- * when unregistered.
- */
- private @Nullable PackageRemovalMonitor mPackageRemovalMonitor;
-
+ private GrantPermissionsViewModel mViewModel;
+ private boolean mResultSet;
/** Package that requested the permission grant */
private String mCallingPackage;
- /** uid of {@link #mCallingPackage} */
- private int mCallingUid;
- /** Notifier for auto-granted permissions */
- private AutoGrantPermissionsNotifier mAutoGrantPermissionsNotifier;
- private PackageInfo mCallingPackageInfo;
-
- private int getPermissionPolicy() {
- DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
- return devicePolicyManager.getPermissionPolicy(null);
- }
-
- /**
- * Try to add a single permission that is requested to be granted.
- *
- * <p>This does <u>not</u> expand the permissions into the {@link #computeAffectedPermissions
- * affected permissions}.
- *
- * @param group The group the permission belongs to (might be a background permission group)
- * @param permName The name of the permission to add
- * @param isFirstInstance Is this the first time the groupStates get created
- */
- private void addRequestedPermissions(AppPermissionGroup group, String permName,
- boolean isFirstInstance) {
- if (!group.isGrantingAllowed()) {
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
- // Skip showing groups that we know cannot be granted.
- return;
- }
-
- Permission permission = group.getPermission(permName);
-
- // If the permission is restricted it does not show in the UI and
- // is not added to the group at all, so check that first.
- if (permission == null && ArrayUtils.contains(
- mAppPermissions.getPackageInfo().requestedPermissions, permName)) {
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION);
- return;
- // We allow the user to choose only non-fixed permissions. A permission
- // is fixed either by device policy or the user denying with prejudice.
- } else if (group.isUserFixed()) {
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED);
- return;
- } else if (group.isPolicyFixed() && !group.areRuntimePermissionsGranted()
- || permission.isPolicyFixed()) {
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED);
- return;
- }
-
- Pair<String, Boolean> groupKey = new Pair<>(group.getName(),
- group.isBackgroundGroup());
-
- GroupState state = mRequestGrantPermissionGroups.get(groupKey);
- if (state == null) {
- state = new GroupState(group);
- mRequestGrantPermissionGroups.put(groupKey, state);
- }
- state.affectedPermissions = ArrayUtils.appendString(
- state.affectedPermissions, permName);
-
- boolean skipGroup = false;
- switch (getPermissionPolicy()) {
- case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: {
- final String[] filterPermissions = new String[]{permName};
- group.grantRuntimePermissions(false, false, filterPermissions);
- group.setPolicyFixed(filterPermissions);
- state.mState = GroupState.STATE_ALLOWED;
- skipGroup = true;
-
- getAutoGrantNotifier().onPermissionAutoGranted(permName);
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED);
- } break;
-
- case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: {
- final String[] filterPermissions = new String[]{permName};
- group.setPolicyFixed(filterPermissions);
- state.mState = GroupState.STATE_DENIED;
- skipGroup = true;
-
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED);
- } break;
-
- default: {
- if (group.areRuntimePermissionsGranted()) {
- group.grantRuntimePermissions(false, false, new String[]{permName});
- state.mState = GroupState.STATE_ALLOWED;
- skipGroup = true;
-
- reportRequestResult(permName,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED);
- }
- } break;
- }
-
- if (skipGroup && isFirstInstance) {
- // Only allow to skip groups when this is the first time the dialog was created.
- // Otherwise the number of groups changes between instances of the dialog.
- state.mState = GroupState.STATE_SKIPPED;
- }
- }
-
- /**
- * Report the result of a grant of a permission.
- *
- * @param permission The permission that was granted or denied
- * @param result The permission grant result
- */
- private void reportRequestResult(@NonNull String permission, int result) {
- boolean isImplicit = !ArrayUtils.contains(mRequestedPermissions, permission);
-
- Log.v(LOG_TAG,
- "Permission grant result requestId=" + mRequestId + " callingUid=" + mCallingUid
- + " callingPackage=" + mCallingPackage + " permission=" + permission
- + " isImplicit=" + isImplicit + " result=" + result);
-
- PermissionControllerStatsLog.write(
- PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, mRequestId,
- mCallingUid, mCallingPackage, permission, isImplicit, result);
- }
-
- /**
- * Report the result of a grant of a permission.
- *
- * @param permissions The permissions that were granted or denied
- * @param result The permission grant result
- */
- private void reportRequestResult(@NonNull String[] permissions, int result) {
- for (String permission : permissions) {
- reportRequestResult(permission, result);
- }
- }
+ private int mTotalRequests = 0;
+ private int mCurrentRequestIdx = 0;
+ private Resources.Theme mOriginalTheme;
+ private View mRootView;
@Override
public void onCreate(Bundle icicle) {
+ mOriginalTheme = getTheme();
+ // Set the activity to be transparent until data is loaded
+ setTheme(R.style.Theme_PermissionGrantDialog_Transparent);
super.onCreate(icicle);
if (icicle == null) {
- mRequestId = new Random().nextLong();
+ mSessionId = new Random().nextLong();
} else {
- mRequestId = icicle.getLong(KEY_REQUEST_ID);
+ mSessionId = icicle.getLong(KEY_SESSION_ID);
}
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
// Cache this as this can only read on onCreate, not later.
mCallingPackage = getCallingPackage();
- try {
- mCouldHaveFgCapabilities = Utils.couldHaveForegroundCapabilities(this, mCallingPackage);
- } catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "Calling package " + mCallingPackage + " not found", e);
+ if (mCallingPackage == null) {
+ Log.e(LOG_TAG, "null callingPackageName. Please use \"RequestPermission\" to "
+ + "request permissions");
+ setResultAndFinish();
+ return;
}
setFinishOnTouchOutside(false);
@@ -314,39 +135,11 @@ public class GrantPermissionsActivity extends Activity
mRequestedPermissions = getIntent().getStringArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
- if (mRequestedPermissions == null) {
- mRequestedPermissions = new String[0];
- }
-
- final int requestedPermCount = mRequestedPermissions.length;
-
- if (requestedPermCount == 0) {
+ if (mRequestedPermissions == null || mRequestedPermissions.length == 0) {
setResultAndFinish();
return;
}
- PackageInfo callingPackageInfo = getCallingPackageInfo();
-
- if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null
- || callingPackageInfo.requestedPermissions.length <= 0) {
- setResultAndFinish();
- return;
- }
-
- // Don't allow legacy apps to request runtime permissions.
- if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
- // Returning empty arrays means a cancellation.
- mRequestedPermissions = new String[0];
- setResultAndFinish();
- return;
- }
-
- mCallingPackageInfo = callingPackageInfo;
-
- mCallingUid = callingPackageInfo.applicationInfo.uid;
-
- UserHandle userHandle = UserHandle.getUserHandleForUid(mCallingUid);
-
if (DeviceUtils.isTelevision(this)) {
mViewHandler = new com.android.permissioncontroller.permission.ui.television
.GrantPermissionsViewHandlerImpl(this,
@@ -358,216 +151,150 @@ public class GrantPermissionsActivity extends Activity
.setResultListener(this);
} else {
mViewHandler = new com.android.permissioncontroller.permission.ui.handheld
- .GrantPermissionsViewHandlerImpl(this, mCallingPackage, userHandle)
- .setResultListener(this);
+ .GrantPermissionsViewHandlerImpl(this, mCallingPackage,
+ Process.myUserHandle()).setResultListener(this);
}
- mAppPermissions = new AppPermissions(this, callingPackageInfo, false,
- new Runnable() {
- @Override
- public void run() {
- setResultAndFinish();
- }
- });
-
- for (String requestedPermission : mRequestedPermissions) {
- if (requestedPermission == null) {
- continue;
- }
-
- ArrayList<String> affectedPermissions =
- computeAffectedPermissions(requestedPermission);
+ GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
+ getApplication(), mCallingPackage, mRequestedPermissions, mSessionId, icicle);
+ mViewModel = factory.create(GrantPermissionsViewModel.class);
+ mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
- int numAffectedPermissions = affectedPermissions.size();
- for (int i = 0; i < numAffectedPermissions; i++) {
- AppPermissionGroup group =
- mAppPermissions.getGroupForPermission(affectedPermissions.get(i));
- if (group == null) {
- reportRequestResult(affectedPermissions.get(i),
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
-
- continue;
- }
-
- if (mAppPermissions.getPackageInfo().applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.R && mRequestedPermissions.length > 1
- && group.isBackgroundGroup()) {
- Log.e(LOG_TAG, "Apps targeting " + Build.VERSION_CODES.R + " must"
- + " have foreground permission before requesting background and must"
- + " request background on its own.");
- finish();
- }
-
- addRequestedPermissions(group, affectedPermissions.get(i), icicle == null);
- }
+ // Restore UI state after lifecycle events. This has to be before we show the first request,
+ // as the UI behaves differently for updates and initial creations.
+ if (icicle != null) {
+ setUpView();
+ mViewHandler.loadInstanceState(icicle);
}
+ }
- int numGroupStates = mRequestGrantPermissionGroups.size();
- for (int groupStateNum = 0; groupStateNum < numGroupStates; groupStateNum++) {
- GroupState groupState = mRequestGrantPermissionGroups.valueAt(groupStateNum);
- AppPermissionGroup group = groupState.mGroup;
+ private void setUpView() {
+ if (mRootView != null) {
+ return;
+ }
- // Restore permission group state after lifecycle events
- if (icicle != null) {
- groupState.mState = icicle.getInt(
- getInstanceStateKey(mRequestGrantPermissionGroups.keyAt(groupStateNum)),
- groupState.mState);
- }
+ setTheme(mOriginalTheme);
- // Do not attempt to grant background access if foreground access is not either already
- // granted or requested
- if (group.isBackgroundGroup()) {
- // Check if a foreground permission is already granted
- boolean foregroundGroupAlreadyGranted = mAppPermissions.getPermissionGroup(
- group.getName()).areRuntimePermissionsGranted();
- boolean hasForegroundRequest = (getForegroundGroupState(group.getName()) != null);
-
- if (!foregroundGroupAlreadyGranted && !hasForegroundRequest) {
- // The background permission cannot be granted at this time
- int numPermissions = groupState.affectedPermissions.length;
- for (int permissionNum = 0; permissionNum < numPermissions; permissionNum++) {
- Log.w(LOG_TAG,
- "Cannot grant " + groupState.affectedPermissions[permissionNum]
- + " as the matching foreground permission is not already "
- + "granted.");
- }
-
- groupState.mState = GroupState.STATE_SKIPPED;
-
- reportRequestResult(groupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
- }
- }
- }
+ mRootView = mViewHandler.createView();
+ mRootView.setVisibility(View.GONE);
+ setContentView(mRootView);
- setContentView(mViewHandler.createView());
Window window = getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
mViewHandler.updateWindowAttributes(layoutParams);
window.setAttributes(layoutParams);
+ }
- // Restore UI state after lifecycle events. This has to be before
- // showNextPermissionGroupGrantRequest is called. showNextPermissionGroupGrantRequest might
- // update the UI and the UI behaves differently for updates and initial creations.
- if (icicle != null) {
- mViewHandler.loadInstanceState(icicle);
- }
-
- if (!showNextPermissionGroupGrantRequest()) {
+ private void onRequestInfoLoad(List<RequestInfo> requests) {
+ if (!mViewModel.getRequestInfosLiveData().isInitialized() || mResultSet) {
+ return;
+ } else if (requests == null) {
+ finish();
+ return;
+ } else if (requests.isEmpty()) {
setResultAndFinish();
+ return;
}
- }
- /**
- * Update the {@link #mRequestedPermissions} if the system reports them as granted.
- *
- * <p>This also updates the {@link #mAppPermissions} state and switches to the next group grant
- * request if the current group becomes granted.
- */
- private void updateIfPermissionsWereGranted() {
- PackageManager pm = getPackageManager();
-
- boolean mightShowNextGroup = true;
- int numGroupStates = mRequestGrantPermissionGroups.size();
- for (int i = 0; i < numGroupStates; i++) {
- GroupState groupState = mRequestGrantPermissionGroups.valueAt(i);
-
- if (groupState == null || groupState.mState != GroupState.STATE_UNKNOWN) {
- // Group has already been approved / denied via the UI by the user
- continue;
- }
-
- boolean allAffectedPermissionsOfThisGroupAreGranted = true;
-
- if (groupState.affectedPermissions == null) {
- // It is not clear which permissions belong to this group, hence never skip this
- // view
- allAffectedPermissionsOfThisGroupAreGranted = false;
- } else {
- for (int permNum = 0; permNum < groupState.affectedPermissions.length;
- permNum++) {
- if (pm.checkPermission(groupState.affectedPermissions[permNum], mCallingPackage)
- == PERMISSION_DENIED) {
- allAffectedPermissionsOfThisGroupAreGranted = false;
- break;
- }
- }
- }
+ setUpView();
- if (allAffectedPermissionsOfThisGroupAreGranted) {
- groupState.mState = GroupState.STATE_ALLOWED;
-
- if (mightShowNextGroup) {
- // The UI currently displays the first group with
- // mState == STATE_UNKNOWN. So we are switching to next group until we
- // could not allow a group that was still unknown
- if (!showNextPermissionGroupGrantRequest()) {
- setResultAndFinish();
- }
- }
- } else {
- mightShowNextGroup = false;
- }
+ if (mRequestInfos == null) {
+ mTotalRequests = requests.size();
}
- }
+ mRequestInfos = requests;
- @Override
- protected void onStart() {
- super.onStart();
+ showNextRequest();
+ }
- try {
- mPermissionChangeListener = new PermissionChangeListener();
- } catch (NameNotFoundException e) {
- setResultAndFinish();
+ private void showNextRequest() {
+ if (mRequestInfos == null || mRequestInfos.isEmpty()) {
return;
}
- PackageManager pm = getPackageManager();
- pm.addOnPermissionsChangeListener(mPermissionChangeListener);
- // get notified when the package is removed
- mPackageRemovalMonitor = new PackageRemovalMonitor(this, mCallingPackage) {
- @Override
- public void onPackageRemoved() {
- Log.w(LOG_TAG, mCallingPackage + " was uninstalled");
+ RequestInfo info = mRequestInfos.get(0);
- finish();
- }
- };
- mPackageRemovalMonitor.register();
-
- // check if the package was removed while this activity was not started
- try {
- pm.getPackageInfo(mCallingPackage, 0);
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, mCallingPackage + " was uninstalled while this activity was stopped", e);
- finish();
+ if (info.getSendToSettingsImmediately()) {
+ mViewModel.sendDirectlyToSettings(this, info.getGroupName());
+ return;
}
- updateIfPermissionsWereGranted();
- }
+ CharSequence appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
+ mCallingPackage, Process.myUserHandle());
- @Override
- protected void onResume() {
- if (!showNextPermissionGroupGrantRequest()) {
- setResultAndFinish();
+ int messageId = 0;
+ switch(info.getMessage()) {
+ case FG_MESSAGE:
+ messageId = Utils.getRequest(info.getGroupName());
+ break;
+ case BG_MESSAGE:
+ messageId = Utils.getBackgroundRequest(info.getGroupName());
+ break;
+ case UPGRADE_MESSAGE:
+ messageId = Utils.getUpgradeRequest(info.getGroupName());
}
- super.onResume();
- }
- @Override
- protected void onStop() {
- super.onStop();
+ CharSequence message = getRequestMessage(appLabel, mCallingPackage,
+ info.getGroupName(), this, messageId);
- if (mPackageRemovalMonitor != null) {
- mPackageRemovalMonitor.unregister();
- mPackageRemovalMonitor = null;
+ int detailMessageId = 0;
+ switch(info.getDetailMessage()) {
+ case FG_MESSAGE:
+ detailMessageId = Utils.getRequestDetail(info.getGroupName());
+ break;
+ case BG_MESSAGE:
+ detailMessageId = Utils.getBackgroundRequestDetail(info.getGroupName());
+ break;
+ case UPGRADE_MESSAGE:
+ detailMessageId = Utils.getUpgradeRequestDetail(info.getGroupName());
+ }
+
+ Spanned detailMessage = null;
+ if (detailMessageId != 0) {
+ detailMessage =
+ new SpannableString(getText(detailMessageId));
+ Annotation[] annotations = detailMessage.getSpans(
+ 0, detailMessage.length(), Annotation.class);
+ int numAnnotations = annotations.length;
+ for (int i = 0; i < numAnnotations; i++) {
+ Annotation annotation = annotations[i];
+ if (annotation.getValue().equals(ANNOTATION_ID)) {
+ int start = detailMessage.getSpanStart(annotation);
+ int end = detailMessage.getSpanEnd(annotation);
+ ClickableSpan clickableSpan = getLinkToAppPermissions(info);
+ SpannableString spannableString =
+ new SpannableString(detailMessage);
+ spannableString.setSpan(clickableSpan, start, end, 0);
+ detailMessage = spannableString;
+ break;
+ }
+ }
}
- if (mPermissionChangeListener != null) {
- getPackageManager().removeOnPermissionsChangeListener(mPermissionChangeListener);
- mPermissionChangeListener = null;
+ Icon icon = null;
+ try {
+ icon = Icon.createWithResource(info.getGroupInfo().getPackageName(),
+ info.getGroupInfo().getIcon());
+ } catch (Resources.NotFoundException e) {
+ Log.e(LOG_TAG, "Cannot load icon for group" + info.getGroupName(), e);
+ }
+
+ boolean showingNewGroup = message == null || !message.equals(getTitle());
+ setTitle(message);
+ ArrayList<Integer> idxs = new ArrayList<>();
+ mButtonVisibilities = new boolean[info.getButtonVisibilities().size()];
+ for (int i = 0; i < info.getButtonVisibilities().size(); i++) {
+ mButtonVisibilities[i] = info.getButtonVisibilities().get(i);
+ if (mButtonVisibilities[i]) {
+ idxs.add(i);
+ }
}
+ mViewHandler.updateUi(info.getGroupName(), mTotalRequests, mCurrentRequestIdx, icon,
+ message, detailMessage, mButtonVisibilities);
+ if (showingNewGroup) {
+ mCurrentRequestIdx++;
+ }
+ mRootView.setVisibility(View.VISIBLE);
}
@Override
@@ -580,423 +307,42 @@ public class GrantPermissionsActivity extends Activity
return super.dispatchTouchEvent(ev);
}
- /**
- * Compose a key that stores the GroupState.mState in the instance state.
- *
- * @param requestGrantPermissionGroupsKey The key of the permission group
- *
- * @return A unique key to be used in the instance state
- */
- private static String getInstanceStateKey(
- Pair<String, Boolean> requestGrantPermissionGroupsKey) {
- return GrantPermissionsActivity.class.getName() + "_"
- + requestGrantPermissionGroupsKey.first + "_"
- + requestGrantPermissionGroupsKey.second;
- }
-
@Override
- protected void onSaveInstanceState(Bundle outState) {
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
mViewHandler.saveInstanceState(outState);
+ mViewModel.saveInstanceState(outState);
- outState.putLong(KEY_REQUEST_ID, mRequestId);
-
- int numGroups = mRequestGrantPermissionGroups.size();
- for (int i = 0; i < numGroups; i++) {
- int state = mRequestGrantPermissionGroups.valueAt(i).mState;
-
- if (state != GroupState.STATE_UNKNOWN) {
- outState.putInt(getInstanceStateKey(mRequestGrantPermissionGroups.keyAt(i)), state);
- }
- }
- }
-
- /**
- * @return the background group state for the permission group with the {@code name}
- */
- private GroupState getBackgroundGroupState(String name) {
- return mRequestGrantPermissionGroups.get(new Pair<>(name, true));
- }
-
- /**
- * @return the foreground group state for the permission group with the {@code name}
- */
- private GroupState getForegroundGroupState(String name) {
- return mRequestGrantPermissionGroups.get(new Pair<>(name, false));
- }
-
- private boolean shouldShowRequestForGroupState(GroupState groupState) {
- if (groupState.mState == GroupState.STATE_SKIPPED) {
- return false;
- }
-
- GroupState foregroundGroup = getForegroundGroupState(groupState.mGroup.getName());
- if (groupState.mGroup.isBackgroundGroup()
- && (foregroundGroup != null && shouldShowRequestForGroupState(foregroundGroup))) {
- // If an app requests both foreground and background permissions of the same group,
- // we only show one request
- return false;
- }
-
- return !mPermissionGroupsToSkip.contains(groupState.mGroup.getName());
+ outState.putLong(KEY_SESSION_ID, mSessionId);
}
- private boolean showNextPermissionGroupGrantRequest() {
- int numGroupStates = mRequestGrantPermissionGroups.size();
- int numGrantRequests = 0;
- for (int i = 0; i < numGroupStates; i++) {
- if (shouldShowRequestForGroupState(mRequestGrantPermissionGroups.valueAt(i))) {
- numGrantRequests++;
- }
- }
-
- int currentIndex = 0;
- List<GroupState> groupStates = new ArrayList<>(mRequestGrantPermissionGroups.values());
- Collections.sort(groupStates, (s1, s2) -> -Boolean.compare(s1.mGroup.supportsOneTimeGrant(),
- s2.mGroup.supportsOneTimeGrant()));
- for (GroupState groupState : groupStates) {
- if (!shouldShowRequestForGroupState(groupState)) {
- continue;
- }
-
- if (groupState.mState == GroupState.STATE_UNKNOWN) {
- GroupState foregroundGroupState;
- GroupState backgroundGroupState;
- if (groupState.mGroup.isBackgroundGroup()) {
- backgroundGroupState = groupState;
- foregroundGroupState = getForegroundGroupState(groupState.mGroup.getName());
- } else {
- foregroundGroupState = groupState;
- backgroundGroupState = getBackgroundGroupState(groupState.mGroup.getName());
- }
-
- CharSequence appLabel = mAppPermissions.getAppLabel();
-
- Icon icon;
- try {
- icon = Icon.createWithResource(groupState.mGroup.getIconPkg(),
- groupState.mGroup.getIconResId());
- } catch (Resources.NotFoundException e) {
- Log.e(LOG_TAG, "Cannot load icon for group" + groupState.mGroup.getName(), e);
- icon = null;
- }
-
- // If no background permissions are granted yet, we need to ask for background
- // permissions
- boolean needBackgroundPermission = false;
- boolean isBackgroundPermissionUserSet = false;
- if (backgroundGroupState != null) {
- if (!backgroundGroupState.mGroup.areRuntimePermissionsGranted()) {
- needBackgroundPermission = true;
- isBackgroundPermissionUserSet = backgroundGroupState.mGroup.isUserSet();
- }
- }
-
- // If no foreground permissions are granted yet, we need to ask for foreground
- // permissions
- boolean needForegroundPermission = false;
- boolean isForegroundPermissionUserSet = false;
- if (foregroundGroupState != null) {
- if (!foregroundGroupState.mGroup.areRuntimePermissionsGranted()) {
- needForegroundPermission = true;
- isForegroundPermissionUserSet = foregroundGroupState.mGroup.isUserSet();
- }
- }
-
- mButtonVisibilities = new boolean[NEXT_BUTTON];
- mButtonVisibilities[ALLOW_BUTTON] = true;
- mButtonVisibilities[DENY_BUTTON] = true;
- mButtonVisibilities[ALLOW_ONE_TIME_BUTTON] =
- groupState.mGroup.supportsOneTimeGrant();
-
- int messageId;
- int detailMessageId = 0;
-
- if (mAppPermissions.getPackageInfo().applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.R) {
- if (groupState.mGroup.hasPermissionWithBackgroundMode()
- || groupState.mGroup.isBackgroundGroup()) {
- if (needForegroundPermission && needBackgroundPermission) {
- if (mCouldHaveFgCapabilities) {
- sendToSettings(groupState);
- return true;
- }
- // Shouldn't be reached as background must be requested as a singleton
- return false;
- } else if (needForegroundPermission) {
- // Case: sdk >= R, BG/FG permission requesting FG only
- messageId = groupState.mGroup.getRequest();
- if (mCouldHaveFgCapabilities) {
- sendToSettings(groupState);
- return true;
- }
- mButtonVisibilities[ALLOW_BUTTON] = false;
- mButtonVisibilities[ALLOW_FOREGROUND_BUTTON] = true;
- mButtonVisibilities[DENY_BUTTON] =
- !isForegroundPermissionUserSet;
- mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isForegroundPermissionUserSet;
- } else if (needBackgroundPermission) {
- // Case: sdk >= R, BG/FG permission requesting BG only
- sendToSettings(groupState);
- return true;
- } else {
- // Not reached as the permissions should be auto-granted
- return false;
- }
- } else {
- // Case: sdk >= R, Requesting normal permission
- messageId = groupState.mGroup.getRequest();
- mButtonVisibilities[DENY_BUTTON] =
- !isForegroundPermissionUserSet;
- mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isForegroundPermissionUserSet;
- if (groupState.mGroup.getName().equals(Manifest.permission_group.CAMERA)
- || groupState.mGroup.getName().equals(
- Manifest.permission_group.MICROPHONE)) {
- mButtonVisibilities[ALLOW_BUTTON] = false;
- if (mCouldHaveFgCapabilities
- || Utils.isEmergencyApp(this, mCallingPackage)) {
- mButtonVisibilities[ALLOW_ALWAYS_BUTTON] = true;
- mButtonVisibilities[ALLOW_ONE_TIME_BUTTON] = false;
- } else {
- mButtonVisibilities[ALLOW_FOREGROUND_BUTTON] = true;
- }
- }
- }
- } else {
- if (groupState.mGroup.hasPermissionWithBackgroundMode()
- || groupState.mGroup.isBackgroundGroup()) {
- if (mCouldHaveFgCapabilities) {
- // Only allow granting of background location
- messageId = groupState.mGroup.getBackgroundRequest();
- detailMessageId = groupState.mGroup.getBackgroundRequestDetail();
- mButtonVisibilities[ALLOW_BUTTON] = false;
- mButtonVisibilities[ALLOW_ONE_TIME_BUTTON] = false;
- mButtonVisibilities[DENY_BUTTON] =
- !isForegroundPermissionUserSet;
- mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isForegroundPermissionUserSet;
- } else {
- if (needForegroundPermission && needBackgroundPermission) {
- // Case: sdk < R, BG/FG permission requesting both
- messageId = groupState.mGroup.getBackgroundRequest();
- detailMessageId = groupState.mGroup.getBackgroundRequestDetail();
- mButtonVisibilities[ALLOW_BUTTON] = false;
- mButtonVisibilities[ALLOW_FOREGROUND_BUTTON] = true;
- mButtonVisibilities[DENY_BUTTON] =
- !isForegroundPermissionUserSet;
- mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isForegroundPermissionUserSet;
- } else if (needForegroundPermission) {
- // Case: sdk < R, BG/FG permission requesting FG only
- messageId = groupState.mGroup.getRequest();
- mButtonVisibilities[ALLOW_BUTTON] = false;
- mButtonVisibilities[ALLOW_FOREGROUND_BUTTON] = true;
- mButtonVisibilities[DENY_BUTTON] =
- !isForegroundPermissionUserSet;
- mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isForegroundPermissionUserSet;
- } else if (needBackgroundPermission) {
- // Case: sdk < R, BG/FG permission requesting BG only
- messageId = groupState.mGroup.getUpgradeRequest();
- detailMessageId = groupState.mGroup.getUpgradeRequestDetail();
- mButtonVisibilities[ALLOW_BUTTON] = false;
- mButtonVisibilities[DENY_BUTTON] = false;
- mButtonVisibilities[ALLOW_ONE_TIME_BUTTON] = false;
- if (mAppPermissions.getPermissionGroup(
- groupState.mGroup.getName()).isOneTime()) {
- mButtonVisibilities[NO_UPGRADE_OT_BUTTON] =
- !isBackgroundPermissionUserSet;
- mButtonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON] =
- isBackgroundPermissionUserSet;
- } else {
- mButtonVisibilities[NO_UPGRADE_BUTTON] =
- !isBackgroundPermissionUserSet;
- mButtonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON] =
- isBackgroundPermissionUserSet;
- }
- } else {
- // Not reached as the permissions should be auto-granted
- return false;
- }
- }
- } else {
- // Case: sdk < R, Requesting normal permission
- messageId = groupState.mGroup.getRequest();
- mButtonVisibilities[DENY_BUTTON] =
- !isForegroundPermissionUserSet;
- mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] =
- isForegroundPermissionUserSet;
- if (groupState.mGroup.getName().equals(Manifest.permission_group.CAMERA)
- || groupState.mGroup.getName().equals(
- Manifest.permission_group.MICROPHONE)) {
- mButtonVisibilities[ALLOW_BUTTON] = false;
- if (mCouldHaveFgCapabilities
- || Utils.isEmergencyApp(this, mCallingPackage)) {
- mButtonVisibilities[ALLOW_ALWAYS_BUTTON] = true;
- mButtonVisibilities[ALLOW_ONE_TIME_BUTTON] = false;
- } else {
- mButtonVisibilities[ALLOW_FOREGROUND_BUTTON] = true;
- }
- }
- }
- }
-
- CharSequence message = getRequestMessage(appLabel, groupState.mGroup, this,
- messageId);
-
- Spanned detailMessage = null;
- if (detailMessageId != 0) {
- detailMessage =
- new SpannableString(getText(detailMessageId));
- Annotation[] annotations = detailMessage.getSpans(
- 0, detailMessage.length(), Annotation.class);
- int numAnnotations = annotations.length;
- for (int i = 0; i < numAnnotations; i++) {
- Annotation annotation = annotations[i];
- if (annotation.getValue().equals(ANNOTATION_ID)) {
- int start = detailMessage.getSpanStart(annotation);
- int end = detailMessage.getSpanEnd(annotation);
- ClickableSpan clickableSpan = getLinkToAppPermissions(groupState);
- SpannableString spannableString =
- new SpannableString(detailMessage);
- spannableString.setSpan(clickableSpan, start, end, 0);
- detailMessage = spannableString;
- mButtonVisibilities[LINK_TO_SETTINGS] = true;
- break;
- }
- }
- }
-
- // Set the permission message as the title so it can be announced.
- setTitle(message);
-
- mViewHandler.updateUi(groupState.mGroup.getName(), numGrantRequests, currentIndex,
- icon, message, detailMessage, mButtonVisibilities);
-
- return true;
- }
-
- if (groupState.mState != GroupState.STATE_SKIPPED) {
- currentIndex++;
- }
- }
-
- return false;
- }
-
- private void sendToSettings(GroupState groupState) {
- if (mActivityResultCallback == null) {
- startAppPermissionFragment(groupState);
- mActivityResultCallback = data -> {
- if (data == null || data.getStringExtra(
- EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
- // User didn't interact, count against rate limit
- if (groupState.mGroup.isUserSet()) {
- groupState.mGroup.setUserFixed(true);
- } else {
- groupState.mGroup.setUserSet(true);
- }
- }
- mPermissionGroupsToSkip.add(groupState.mGroup.getName());
- };
- }
- }
-
- private ClickableSpan getLinkToAppPermissions(GroupState groupState) {
+ private ClickableSpan getLinkToAppPermissions(RequestInfo info) {
return new ClickableSpan() {
@Override
public void onClick(View widget) {
- logGrantPermissionActivityButtons(groupState.mGroup.getName(), LINKED_TO_SETTINGS);
- startAppPermissionFragment(groupState);
- mActivityResultCallback = data -> {
- if (data != null) {
- String groupName = data.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED);
- if (groupName != null) {
- mPermissionGroupsToSkip.add(groupName);
- int result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, -1);
- logSettingsInteraction(groupName, result);
- }
- }
- };
+ logGrantPermissionActivityButtons(info.getGroupName(), LINKED_TO_SETTINGS);
+ mViewModel.sendToSettingsFromLink(GrantPermissionsActivity.this,
+ info.getGroupName());
}
};
}
- private void logSettingsInteraction(String groupName, int result) {
- GroupState foregroundGroupState = getForegroundGroupState(groupName);
- GroupState backgroundGroupState = getBackgroundGroupState(groupName);
- switch (result) {
- case GRANTED_ALWAYS:
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS);
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS);
- }
- break;
- case GRANTED_FOREGROUND_ONLY:
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS);
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS);
- }
- break;
- case DENIED:
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS);
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS);
- }
- break;
- case DENIED_DO_NOT_ASK_AGAIN:
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS);
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS);
- }
- break;
- }
- }
-
- private void startAppPermissionFragment(GroupState groupState) {
- Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
- .putExtra(Intent.EXTRA_PACKAGE_NAME, mAppPermissions.getPackageInfo().packageName)
- .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupState.mGroup.getName())
- .putExtra(Intent.EXTRA_USER, groupState.mGroup.getUser())
- .putExtra(EXTRA_CALLER_NAME, GrantPermissionsActivity.class.getName())
- .putExtra(Constants.EXTRA_SESSION_ID, mRequestId)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE);
- }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == APP_PERMISSION_REQUEST_CODE && mActivityResultCallback != null) {
- mActivityResultCallback.accept(data);
- mActivityResultCallback = null;
+ super.onActivityResult(requestCode, resultCode, data);
+ Consumer<Intent> callback = mViewModel.getActivityResultCallback();
+
+ if (requestCode == APP_PERMISSION_REQUEST_CODE && callback != null) {
+ callback.accept(data);
+ mViewModel.setActivityResultCallback(null);
}
}
@Override
public void onPermissionGrantResult(String name,
@GrantPermissionsViewHandler.Result int result) {
- GroupState foregroundGroupState = getForegroundGroupState(name);
- GroupState backgroundGroupState = getBackgroundGroupState(name);
-
if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
|| result == DENIED_DO_NOT_ASK_AGAIN) {
KeyguardManager kgm = getSystemService(KeyguardManager.class);
@@ -1005,8 +351,8 @@ public class GrantPermissionsActivity extends Activity
kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
@Override
public void onDismissError() {
- Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name + " result="
- + result);
+ Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name
+ + " result=" + result);
}
@Override
@@ -1027,110 +373,13 @@ public class GrantPermissionsActivity extends Activity
}
logGrantPermissionActivityButtons(name, result);
- switch (result) {
- case CANCELED:
- if (foregroundGroupState != null) {
- reportRequestResult(foregroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED);
- }
- if (backgroundGroupState != null) {
- reportRequestResult(backgroundGroupState.affectedPermissions,
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED);
- }
- setResultAndFinish();
- return;
- case GRANTED_ALWAYS :
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState, true, false, false);
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState, true, false, false);
- }
- break;
- case GRANTED_FOREGROUND_ONLY :
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState, true, false, false);
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState, false, false, false);
- }
- break;
- case GRANTED_ONE_TIME:
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState, true, true, false);
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState, false, true, false);
- }
- break;
- case DENIED :
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState, false, false, false);
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState, false, false, false);
- }
- break;
- case DENIED_DO_NOT_ASK_AGAIN :
- if (foregroundGroupState != null) {
- onPermissionGrantResultSingleState(foregroundGroupState, false, false, true);
- }
- if (backgroundGroupState != null) {
- onPermissionGrantResultSingleState(backgroundGroupState, false, false, true);
- }
- break;
- }
-
- if (!showNextPermissionGroupGrantRequest()) {
+ mViewModel.onPermissionGrantResult(name, result);
+ showNextRequest();
+ if (result == CANCELED) {
setResultAndFinish();
}
}
- /**
- * Grants or revoked the affected permissions for a single {@link GroupState}.
- *
- * @param groupState The group state with the permissions to grant/revoke
- * @param granted {@code true} if the permissions should be granted, {@code false} if they
- * should be revoked
- * @param isOneTime if the permission is temporary and should be revoked automatically
- * @param doNotAskAgain if the permissions should be revoked should be app be allowed to ask
- * again for the same permissions?
- */
- private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,
- boolean isOneTime, boolean doNotAskAgain) {
- if (groupState != null && groupState.mGroup != null
- && groupState.mState == GroupState.STATE_UNKNOWN) {
- if (granted) {
- int permissionGrantRequestResult =
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED;
-
- if (isOneTime) {
- groupState.mGroup.setOneTime(true);
- permissionGrantRequestResult =
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME;
- } else {
- groupState.mGroup.setOneTime(false);
- }
-
- groupState.mGroup.grantRuntimePermissions(true, doNotAskAgain,
- groupState.affectedPermissions);
- groupState.mState = GroupState.STATE_ALLOWED;
-
- reportRequestResult(groupState.affectedPermissions, permissionGrantRequestResult);
- } else {
- groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
- groupState.affectedPermissions);
- groupState.mGroup.setOneTime(false);
- groupState.mState = GroupState.STATE_DENIED;
-
- reportRequestResult(groupState.affectedPermissions, doNotAskAgain
- ?
- PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
- : PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED);
- }
- }
- }
-
@Override
public void onBackPressed() {
mViewHandler.onBackPressed();
@@ -1139,37 +388,30 @@ public class GrantPermissionsActivity extends Activity
@Override
public void finish() {
setResultIfNeeded(RESULT_CANCELED);
- if (mAutoGrantPermissionsNotifier != null) {
- mAutoGrantPermissionsNotifier.notifyOfAutoGrantPermissions(true);
- }
+ mViewModel.autoGrantNotify();
super.finish();
}
- private PackageInfo getCallingPackageInfo() {
- try {
- return getPackageManager().getPackageInfo(mCallingPackage,
- PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- Log.i(LOG_TAG, "No package: " + mCallingPackage, e);
- return null;
- }
- }
-
private void setResultIfNeeded(int resultCode) {
if (!mResultSet) {
mResultSet = true;
- logRequestedPermissionGroups();
+ mViewModel.logRequestedPermissionGroups();
Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);
- PackageManager pm = getPackageManager();
- int numRequestedPermissions = mRequestedPermissions.length;
- int[] grantResults = new int[numRequestedPermissions];
- for (int i = 0; i < numRequestedPermissions; i++) {
- grantResults[i] = pm.checkPermission(mRequestedPermissions[i], mCallingPackage);
- }
+ if (mViewModel.getRequestInfosLiveData().isInitialized()
+ && mViewModel.shouldReturnPermissionState()) {
+ PackageManager pm = getPackageManager();
+ int numRequestedPermissions = mRequestedPermissions.length;
+ int[] grantResults = new int[numRequestedPermissions];
+ for (int i = 0; i < numRequestedPermissions; i++) {
+ grantResults[i] = pm.checkPermission(mRequestedPermissions[i], mCallingPackage);
+ }
- result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
+ result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
+ } else {
+ result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, new int[0]);
+ }
setResult(resultCode, result);
}
}
@@ -1179,79 +421,6 @@ public class GrantPermissionsActivity extends Activity
finish();
}
- private void logRequestedPermissionGroups() {
- if (mRequestGrantPermissionGroups.isEmpty()) {
- return;
- }
-
- final int groupCount = mRequestGrantPermissionGroups.size();
- List<AppPermissionGroup> groups = new ArrayList<>(groupCount);
- for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
- groups.add(groupState.mGroup);
- }
-
- SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups);
- }
-
- /**
- * Get the actually requested permissions when a permission is requested.
- *
- * <p>>In some cases requesting to grant a single permission requires the system to grant
- * additional permissions. E.g. before N-MR1 a single permission of a group caused the whole
- * group to be granted. Another case are permissions that are split into two. For apps that
- * target an SDK before the split, this method automatically adds the split off permission.
- *
- * @param permission The requested permission
- *
- * @return The actually requested permissions
- */
- private ArrayList<String> computeAffectedPermissions(String permission) {
- int requestingAppTargetSDK =
- mAppPermissions.getPackageInfo().applicationInfo.targetSdkVersion;
-
- // If a permission is split, all permissions the original permission is split into are
- // affected
- ArrayList<String> extendedBySplitPerms = new ArrayList<>();
- extendedBySplitPerms.add(permission);
-
- List<PermissionManager.SplitPermissionInfo> splitPerms = getSystemService(
- PermissionManager.class).getSplitPermissions();
- int numSplitPerms = splitPerms.size();
- for (int i = 0; i < numSplitPerms; i++) {
- PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i);
-
- if (requestingAppTargetSDK < splitPerm.getTargetSdk()
- && permission.equals(splitPerm.getSplitPermission())) {
- extendedBySplitPerms.addAll(splitPerm.getNewPermissions());
- }
- }
-
- // For <= N_MR1 apps all permissions of the groups of the requested permissions are affected
- if (requestingAppTargetSDK <= Build.VERSION_CODES.N_MR1) {
- ArrayList<String> extendedBySplitPermsAndGroup = new ArrayList<>();
-
- int numExtendedBySplitPerms = extendedBySplitPerms.size();
- for (int splitPermNum = 0; splitPermNum < numExtendedBySplitPerms; splitPermNum++) {
- AppPermissionGroup group = mAppPermissions.getGroupForPermission(
- extendedBySplitPerms.get(splitPermNum));
-
- if (group == null) {
- continue;
- }
-
- ArrayList<Permission> permissionsInGroup = group.getPermissions();
- int numPermissionsInGroup = permissionsInGroup.size();
- for (int permNum = 0; permNum < numPermissionsInGroup; permNum++) {
- extendedBySplitPermsAndGroup.add(permissionsInGroup.get(permNum).getName());
- }
- }
-
- return extendedBySplitPermsAndGroup;
- } else {
- return extendedBySplitPerms;
- }
- }
-
private void logGrantPermissionActivityButtons(String permissionGroupName, int grantResult) {
int clickedButton = 0;
int presentedButtons = getButtonState();
@@ -1295,13 +464,7 @@ public class GrantPermissionsActivity extends Activity
break;
}
- PermissionControllerStatsLog.write(GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS,
- permissionGroupName, mCallingUid, mCallingPackage, presentedButtons,
- clickedButton, mRequestId);
- Log.v(LOG_TAG, "Logged buttons presented and clicked permissionGroupName="
- + permissionGroupName + " uid=" + mCallingUid + " package=" + mCallingPackage
- + " presentedButtons=" + presentedButtons + " clickedButton=" + clickedButton
- + " sessionId=" + mRequestId);
+ mViewModel.logClickedButtons(permissionGroupName, clickedButton, presentedButtons);
}
private int getButtonState() {
@@ -1317,51 +480,4 @@ public class GrantPermissionsActivity extends Activity
}
return buttonState;
}
-
- private static final class GroupState {
- static final int STATE_UNKNOWN = 0;
- static final int STATE_ALLOWED = 1;
- static final int STATE_DENIED = 2;
- static final int STATE_SKIPPED = 3;
-
- final AppPermissionGroup mGroup;
- int mState = STATE_UNKNOWN;
-
- /** Permissions of this group that need to be granted, null == no permissions of group */
- String[] affectedPermissions;
-
- GroupState(AppPermissionGroup group) {
- mGroup = group;
- }
- }
-
- private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
- final int mCallingPackageUid;
-
- PermissionChangeListener() throws NameNotFoundException {
- mCallingPackageUid = getPackageManager().getPackageUid(mCallingPackage, 0);
- }
-
- @Override
- public void onPermissionsChanged(int uid) {
- if (uid == mCallingPackageUid) {
- updateIfPermissionsWereGranted();
- }
- }
- }
-
- /**
- * Creates the AutoGrantPermissionsNotifier lazily in case there's no policy set
- * device-wide (common case).
- *
- * @return An initalized {@code AutoGrantPermissionsNotifier} instance.
- */
- private @NonNull AutoGrantPermissionsNotifier getAutoGrantNotifier() {
- if (mAutoGrantPermissionsNotifier == null) {
- mAutoGrantPermissionsNotifier = new AutoGrantPermissionsNotifier(
- this, mCallingPackageInfo);
- }
-
- return mAutoGrantPermissionsNotifier;
- }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
index 6a7002d3b..7a0a843a6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
@@ -431,6 +431,10 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
}
private void setResult(@GrantPermissionsViewHandler.Result int result) {
+ if (!mPackageName.equals(
+ getActivity().getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME))) {
+ return;
+ }
Intent intent = new Intent()
.putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName)
.putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
deleted file mode 100644
index 04a5f439a..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2015 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.ui.handheld;
-
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON;
-import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON;
-
-import android.app.Activity;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.text.method.LinkMovementMethod;
-import android.transition.ChangeBounds;
-import android.transition.TransitionManager;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.AnimationUtils;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity;
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
-
-public class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler,
- OnClickListener {
-
- public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
- public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT";
- public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX";
- public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON";
- public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE";
- private static final String ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE";
- private static final String ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES";
-
- // Animation parameters.
- private static final long SWITCH_TIME_MILLIS = 75;
- private static final long ANIMATION_DURATION_MILLIS = 200;
-
- private static final SparseArray<Integer> BUTTON_RES_ID_TO_NUM = new SparseArray<>();
- static {
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_button, ALLOW_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_always_button,
- ALLOW_ALWAYS_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button,
- ALLOW_FOREGROUND_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_button, DENY_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_and_dont_ask_again_button,
- DENY_AND_DONT_ASK_AGAIN_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_one_time_button, ALLOW_ONE_TIME_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_button, NO_UPGRADE_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_and_dont_ask_again_button,
- NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_button, NO_UPGRADE_OT_BUTTON);
- BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button,
- NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON);
- }
-
- private final Activity mActivity;
- private final String mAppPackageName;
- private final UserHandle mUserHandle;
-
- private ResultListener mResultListener;
-
- // Configuration of the current dialog
- private String mGroupName;
- private int mGroupCount;
- private int mGroupIndex;
- private Icon mGroupIcon;
- private CharSequence mGroupMessage;
- private CharSequence mDetailMessage;
- private boolean[] mButtonVisibilities;
-
- // Views
- private ImageView mIconView;
- private TextView mMessageView;
- private TextView mDetailMessageView;
- private Button[] mButtons;
- private ViewGroup mRootView;
-
- public GrantPermissionsViewHandlerImpl(Activity activity, String appPackageName,
- @NonNull UserHandle userHandle) {
- mActivity = activity;
- mAppPackageName = appPackageName;
- mUserHandle = userHandle;
- }
-
- @Override
- public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) {
- mResultListener = listener;
- return this;
- }
-
- @Override
- public void saveInstanceState(Bundle arguments) {
- arguments.putString(ARG_GROUP_NAME, mGroupName);
- arguments.putInt(ARG_GROUP_COUNT, mGroupCount);
- arguments.putInt(ARG_GROUP_INDEX, mGroupIndex);
- arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon);
- arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage);
- arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, mDetailMessage);
- arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, mButtonVisibilities);
- }
-
- @Override
- public void loadInstanceState(Bundle savedInstanceState) {
- mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
- mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE);
- mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON);
- mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT);
- mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX);
- mDetailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE);
- mButtonVisibilities = savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES);
-
- updateAll();
- }
-
- @Override
- public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
- CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities) {
- boolean isNewGroup = mGroupIndex != groupIndex;
-
- mGroupName = groupName;
- mGroupCount = groupCount;
- mGroupIndex = groupIndex;
- mGroupIcon = icon;
- mGroupMessage = message;
- mDetailMessage = detailMessage;
- mButtonVisibilities = buttonVisibilities;
-
- // If this is a second (or later) permission and the views exist, then animate.
- if (mIconView != null) {
- updateAll();
- }
- }
-
- private void updateAll() {
- updateDescription();
- updateDetailDescription();
- updateButtons();
-
-// Animate change in size
-// Grow or shrink the content container to size of new content
- ChangeBounds growShrinkToNewContentSize = new ChangeBounds();
- growShrinkToNewContentSize.setDuration(ANIMATION_DURATION_MILLIS);
- growShrinkToNewContentSize.setInterpolator(AnimationUtils.loadInterpolator(mActivity,
- android.R.interpolator.fast_out_slow_in));
- TransitionManager.beginDelayedTransition(mRootView, growShrinkToNewContentSize);
- }
-
- @Override
- public View createView() {
- mRootView = (ViewGroup) LayoutInflater.from(mActivity)
- .inflate(R.layout.grant_permissions, null);
-
- int h = mActivity.getResources().getDisplayMetrics().heightPixels;
- mRootView.setMinimumHeight(h);
- mRootView.findViewById(R.id.grant_singleton).setOnClickListener(this); // Cancel dialog
- mRootView.findViewById(R.id.grant_dialog).setOnClickListener(this); // Swallow click event
-
- mMessageView = mRootView.findViewById(R.id.permission_message);
- mDetailMessageView = mRootView.findViewById(R.id.detail_message);
- mDetailMessageView.setMovementMethod(LinkMovementMethod.getInstance());
- mIconView = mRootView.findViewById(R.id.permission_icon);
-
- mButtons = new Button[GrantPermissionsActivity.NEXT_BUTTON];
-
- int numButtons = BUTTON_RES_ID_TO_NUM.size();
- for (int i = 0; i < numButtons; i++) {
- Button button = mRootView.findViewById(BUTTON_RES_ID_TO_NUM.keyAt(i));
- button.setOnClickListener(this);
- mButtons[BUTTON_RES_ID_TO_NUM.valueAt(i)] = button;
- }
-
- if (mGroupName != null) {
- updateAll();
- }
-
- return mRootView;
- }
-
- @Override
- public void updateWindowAttributes(LayoutParams outLayoutParams) {
- // No-op
- }
-
- private void updateDescription() {
- if (mGroupIcon != null) {
- mIconView.setImageDrawable(mGroupIcon.loadDrawable(mActivity));
- }
- mMessageView.setText(mGroupMessage);
- }
-
- private void updateDetailDescription() {
- if (mDetailMessage == null) {
- mDetailMessageView.setVisibility(View.GONE);
- } else {
- mDetailMessageView.setText(mDetailMessage);
- mDetailMessageView.setVisibility(View.VISIBLE);
- }
- }
-
- private void updateButtons() {
- int numButtons = BUTTON_RES_ID_TO_NUM.size();
- for (int i = 0; i < numButtons; i++) {
- int pos = BUTTON_RES_ID_TO_NUM.valueAt(i);
- mButtons[pos].setVisibility(mButtonVisibilities[pos] ? View.VISIBLE : View.GONE);
- }
- }
-
- @Override
- public void onClick(View view) {
- int id = view.getId();
- if (id == R.id.grant_singleton) {
- if (mResultListener != null) {
- mResultListener.onPermissionGrantResult(mGroupName, CANCELED);
- } else {
- mActivity.finish();
- }
- return;
- }
- int button = -1;
- try {
- button = BUTTON_RES_ID_TO_NUM.get(id);
- } catch (NullPointerException e) {
- // Clicked a view which is not one of the defined buttons
- return;
- }
- switch (button) {
- case ALLOW_BUTTON:
- if (mResultListener != null) {
- view.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
- mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ALWAYS);
- }
- break;
- case ALLOW_FOREGROUND_BUTTON:
- if (mResultListener != null) {
- view.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
- mResultListener.onPermissionGrantResult(mGroupName,
- GRANTED_FOREGROUND_ONLY);
- }
- break;
- case ALLOW_ALWAYS_BUTTON:
- if (mResultListener != null) {
- view.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
- mResultListener.onPermissionGrantResult(mGroupName,
- GRANTED_ALWAYS);
- }
- break;
- case ALLOW_ONE_TIME_BUTTON:
- if (mResultListener != null) {
- view.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
- mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ONE_TIME);
- }
- break;
- case DENY_BUTTON:
- case NO_UPGRADE_BUTTON:
- case NO_UPGRADE_OT_BUTTON:
- if (mResultListener != null) {
- view.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
- mResultListener.onPermissionGrantResult(mGroupName, DENIED);
- }
- break;
- case DENY_AND_DONT_ASK_AGAIN_BUTTON:
- case NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON:
- case NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON:
- if (mResultListener != null) {
- view.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
- mResultListener.onPermissionGrantResult(mGroupName,
- DENIED_DO_NOT_ASK_AGAIN);
- }
- break;
- }
-
- }
-
- @Override
- public void onBackPressed() {
- if (mResultListener != null) {
- mResultListener.onPermissionGrantResult(mGroupName, CANCELED);
- } else {
- mActivity.finish();
- }
- }
-
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
new file mode 100644
index 000000000..4d5091403
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2015 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.ui.handheld
+
+import android.app.Activity
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.UserHandle
+import android.text.method.LinkMovementMethod
+import android.transition.ChangeBounds
+import android.transition.TransitionManager
+import android.util.SparseIntArray
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.animation.AnimationUtils
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.TextView
+
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME
+
+class GrantPermissionsViewHandlerImpl(
+ private val mActivity: Activity,
+ private val mAppPackageName: String,
+ private val mUserHandle: UserHandle
+) : GrantPermissionsViewHandler, OnClickListener {
+
+ private var resultListener: GrantPermissionsViewHandler.ResultListener? = null
+
+ // Configuration of the current dialog
+ private var groupName: String? = null
+ private var groupCount: Int = 0
+ private var groupIndex: Int = 0
+ private var groupIcon: Icon? = null
+ private var groupMessage: CharSequence? = null
+ private var detailMessage: CharSequence? = null
+ private var buttonVisibilities: BooleanArray? = null
+
+ // Views
+ private var iconView: ImageView? = null
+ private var messageView: TextView? = null
+ private var detailMessageView: TextView? = null
+ private var buttons: Array<Button?>? = null
+ private var rootView: ViewGroup? = null
+
+ override fun setResultListener(
+ listener: GrantPermissionsViewHandler.ResultListener
+ ): GrantPermissionsViewHandlerImpl {
+ resultListener = listener
+ return this
+ }
+
+ override fun saveInstanceState(arguments: Bundle) {
+ arguments.putString(ARG_GROUP_NAME, groupName)
+ arguments.putInt(ARG_GROUP_COUNT, groupCount)
+ arguments.putInt(ARG_GROUP_INDEX, groupIndex)
+ arguments.putParcelable(ARG_GROUP_ICON, groupIcon)
+ arguments.putCharSequence(ARG_GROUP_MESSAGE, groupMessage)
+ arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, detailMessage)
+ arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, buttonVisibilities)
+ }
+
+ override fun loadInstanceState(savedInstanceState: Bundle) {
+ groupName = savedInstanceState.getString(ARG_GROUP_NAME)
+ groupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE)
+ groupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON)
+ groupCount = savedInstanceState.getInt(ARG_GROUP_COUNT)
+ groupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX)
+ detailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE)
+ buttonVisibilities = savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES)
+
+ updateAll()
+ }
+
+ override fun updateUi(
+ groupName: String,
+ groupCount: Int,
+ groupIndex: Int,
+ icon: Icon,
+ message: CharSequence,
+ detailMessage: CharSequence?,
+ buttonVisibilities: BooleanArray
+ ) {
+
+ this.groupName = groupName
+ this.groupCount = groupCount
+ this.groupIndex = groupIndex
+ groupIcon = icon
+ groupMessage = message
+ this.detailMessage = detailMessage
+ this.buttonVisibilities = buttonVisibilities
+
+ // If this is a second (or later) permission and the views exist, then animate.
+ if (iconView != null) {
+ updateAll()
+ }
+ }
+
+ private fun updateAll() {
+ updateDescription()
+ updateDetailDescription()
+ updateButtons()
+
+ // Animate change in size
+ // Grow or shrink the content container to size of new content
+ val growShrinkToNewContentSize = ChangeBounds()
+ growShrinkToNewContentSize.duration = ANIMATION_DURATION_MILLIS
+ growShrinkToNewContentSize.interpolator = AnimationUtils.loadInterpolator(mActivity,
+ android.R.interpolator.fast_out_slow_in)
+ TransitionManager.beginDelayedTransition(rootView, growShrinkToNewContentSize)
+ }
+
+ override fun createView(): View {
+ val rootView = LayoutInflater.from(mActivity)
+ .inflate(R.layout.grant_permissions, null) as ViewGroup
+ this.rootView = rootView
+
+ val h = mActivity.resources.displayMetrics.heightPixels
+ rootView.minimumHeight = h
+ // Cancel dialog
+ rootView.findViewById<View>(R.id.grant_singleton)!!.setOnClickListener(this)
+ // Swallow click event
+ rootView.findViewById<View>(R.id.grant_dialog)!!.setOnClickListener(this)
+
+ messageView = rootView.findViewById(R.id.permission_message)
+ detailMessageView = rootView.findViewById(R.id.detail_message)
+ detailMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+ iconView = rootView.findViewById(R.id.permission_icon)
+
+ val buttons = arrayOfNulls<Button>(NEXT_BUTTON)
+
+ val numButtons = BUTTON_RES_ID_TO_NUM.size()
+ for (i in 0 until numButtons) {
+ val button = rootView.findViewById<Button>(BUTTON_RES_ID_TO_NUM.keyAt(i))
+ button!!.setOnClickListener(this)
+ buttons[BUTTON_RES_ID_TO_NUM.valueAt(i)] = button
+ }
+
+ this.buttons = buttons
+ if (groupName != null) {
+ updateAll()
+ }
+
+ return rootView
+ }
+
+ override fun updateWindowAttributes(outLayoutParams: LayoutParams) {
+ // No-op
+ }
+
+ private fun updateDescription() {
+ if (groupIcon != null) {
+ iconView!!.setImageDrawable(groupIcon!!.loadDrawable(mActivity))
+ }
+ messageView!!.text = groupMessage
+ }
+
+ private fun updateDetailDescription() {
+ if (detailMessage == null) {
+ detailMessageView!!.visibility = View.GONE
+ } else {
+ detailMessageView!!.text = detailMessage
+ detailMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ private fun updateButtons() {
+ val numButtons = BUTTON_RES_ID_TO_NUM.size()
+ for (i in 0 until numButtons) {
+ val pos = BUTTON_RES_ID_TO_NUM.valueAt(i)
+ buttons!![pos]!!.visibility = if (buttonVisibilities!![pos]) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ }
+ }
+
+ override fun onClick(view: View) {
+ val id = view.id
+ if (id == R.id.grant_singleton) {
+ if (resultListener != null) {
+ resultListener!!.onPermissionGrantResult(groupName, CANCELED)
+ } else {
+ mActivity.finish()
+ }
+ return
+ }
+ var button = -1
+ try {
+ button = BUTTON_RES_ID_TO_NUM.get(id)
+ } catch (e: NullPointerException) {
+ // Clicked a view which is not one of the defined buttons
+ return
+ }
+
+ when (button) {
+ ALLOW_BUTTON -> if (resultListener != null) {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener!!.onPermissionGrantResult(groupName, GRANTED_ALWAYS)
+ }
+ ALLOW_FOREGROUND_BUTTON -> if (resultListener != null) {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener!!.onPermissionGrantResult(groupName, GRANTED_FOREGROUND_ONLY)
+ }
+ ALLOW_ALWAYS_BUTTON -> if (resultListener != null) {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener!!.onPermissionGrantResult(groupName, GRANTED_ALWAYS)
+ }
+ ALLOW_ONE_TIME_BUTTON -> if (resultListener != null) {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener!!.onPermissionGrantResult(groupName, GRANTED_ONE_TIME)
+ }
+ DENY_BUTTON, NO_UPGRADE_BUTTON, NO_UPGRADE_OT_BUTTON -> if (resultListener != null) {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener!!.onPermissionGrantResult(groupName, DENIED)
+ }
+ DENY_AND_DONT_ASK_AGAIN_BUTTON, NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON,
+ NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON -> if (resultListener != null) {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener!!.onPermissionGrantResult(groupName, DENIED_DO_NOT_ASK_AGAIN)
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+ if (resultListener == null) {
+ mActivity.finish()
+ return
+ }
+ resultListener?.onPermissionGrantResult(groupName, CANCELED)
+ }
+
+ companion object {
+
+ val ARG_GROUP_NAME = "ARG_GROUP_NAME"
+ val ARG_GROUP_COUNT = "ARG_GROUP_COUNT"
+ val ARG_GROUP_INDEX = "ARG_GROUP_INDEX"
+ val ARG_GROUP_ICON = "ARG_GROUP_ICON"
+ val ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"
+ private val ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE"
+ private val ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES"
+
+ // Animation parameters.
+ private val SWITCH_TIME_MILLIS: Long = 75
+ private val ANIMATION_DURATION_MILLIS: Long = 200
+
+ private val BUTTON_RES_ID_TO_NUM = SparseIntArray()
+
+ init {
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_button, ALLOW_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_always_button,
+ ALLOW_ALWAYS_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button,
+ ALLOW_FOREGROUND_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_button, DENY_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_and_dont_ask_again_button,
+ DENY_AND_DONT_ASK_AGAIN_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_one_time_button,
+ ALLOW_ONE_TIME_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_button,
+ NO_UPGRADE_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_and_dont_ask_again_button,
+ NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_button,
+ NO_UPGRADE_OT_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button,
+ NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON)
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java
index 1b8d8a098..d75e61133 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java
@@ -575,7 +575,8 @@ class PermissionPreference extends MultiTargetSwitchPreference {
Bundle args = new Bundle();
args.putCharSequence(BackgroundAccessChooser.TITLE,
- getRequestMessage(getAppLabel(), mGroup, getContext(), mGroup.getRequest()));
+ getRequestMessage(getAppLabel(), mGroup.getApp().packageName, mGroup.getName(),
+ getContext(), mGroup.getRequest()));
args.putString(BackgroundAccessChooser.KEY, getKey());
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
new file mode 100644
index 000000000..238a92999
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -0,0 +1,989 @@
+/*
+ * 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.ui.model
+
+import android.app.Activity
+import android.app.Application
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.permission.PermissionManager
+import android.util.Log
+import androidx.core.util.Consumer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.PermissionControllerStatsLog
+import com.android.permissioncontroller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED
+import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
+import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
+import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
+import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.data.get
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo
+import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_SETTINGS
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.SafetyNetLogger
+import com.android.permissioncontroller.permission.utils.Utils
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+/**
+ * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by
+ * the permissions requested by the user, and generates a RequestInfo object for each group, if
+ * action is needed. It will not return any data if one of the requests is malformed.
+ *
+ * @param app: The current application
+ * @param packageName: The packageName permissions are being requested for
+ * @param requestedPermissions: The list of permissions requested
+ * @param sessionId: A long to identify this session
+ * @param storedState: Previous state, if this activity was stopped and is being recreated
+ */
+class GrantPermissionsViewModel(
+ private val app: Application,
+ private val packageName: String,
+ private val requestedPermissions: List<String>,
+ private val sessionId: Long,
+ private val storedState: Bundle?
+) : ViewModel() {
+ private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
+ private val user = Process.myUserHandle()
+ private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
+ private val permissionPolicy = app.getSystemService(DevicePolicyManager::class.java)!!
+ .getPermissionPolicy(null)
+ private val permGroupsToSkip = mutableListOf<String>()
+ private var groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>()
+
+ private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null
+ private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier {
+ autoGrantNotifier = AutoGrantPermissionsNotifier(app, packageInfo.toPackageInfo(app)!!)
+ return autoGrantNotifier!!
+ }
+
+ private lateinit var packageInfo: LightPackageInfo
+
+ // All permissions that could possibly be affected by the provided requested permissions, before
+ // filtering system fixed, auto grant, etc.
+ private var unfilteredAffectedPermissions = requestedPermissions
+
+ /**
+ * A class which represents a correctly requested permission group, and the buttons and messages
+ * which should be shown with it.
+ */
+ data class RequestInfo(
+ val groupInfo: LightPermGroupInfo,
+ val buttonVisibilities: List<Boolean> = List(NEXT_BUTTON) { false },
+ val message: RequestMessage = RequestMessage.FG_MESSAGE,
+ val detailMessage: RequestMessage = RequestMessage.NO_MESSAGE,
+ val sendToSettingsImmediately: Boolean = false
+ ) {
+ val groupName = groupInfo.name
+ }
+
+ var activityResultCallback: Consumer<Intent>? = null
+
+ /**
+ * A LiveData which holds a list of the currently pending RequestInfos
+ */
+ val requestInfosLiveData = object :
+ SmartUpdateMediatorLiveData<List<RequestInfo>>() {
+ private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
+ private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
+ private val appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>()
+
+ init {
+ GlobalScope.launch(Main.immediate) {
+ val groups = packagePermissionsLiveData.getInitializedValue()
+ if (groups == null || groups.isEmpty()) {
+ Log.w(LOG_TAG, "Package $packageName not found")
+ value = null
+ return@launch
+ }
+ packageInfo = packageInfoLiveData.getInitializedValue()
+
+ if (packageInfo.requestedPermissions.isEmpty() ||
+ packageInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ Log.w(LOG_TAG, "Package $packageName has no requested permissions, or " +
+ "is a pre-M app")
+ value = null
+ return@launch
+ }
+
+ val allAffectedPermissions = requestedPermissions.toMutableSet()
+ for (requestedPerm in requestedPermissions) {
+ allAffectedPermissions.addAll(computeAffectedPermissions(requestedPerm, groups))
+ }
+ unfilteredAffectedPermissions = allAffectedPermissions.toList()
+
+ getAppPermGroups(groups.toMutableMap().apply {
+ remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS)
+ })
+ }
+ }
+
+ private fun getAppPermGroups(groups: Map<String, List<String>>) {
+
+ val requestedGroups = groups.filter { (_, perms) ->
+ perms.any { it in unfilteredAffectedPermissions }
+ }
+
+ if (requestedGroups.isEmpty()) {
+ Log.e(LOG_TAG, "None of " +
+ "$unfilteredAffectedPermissions in $groups")
+ value = null
+ return
+ }
+
+ val getLiveDataFun = { groupName: String ->
+ LightAppPermGroupLiveData[packageName, groupName, user]
+ }
+ setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun)
+ }
+
+ override fun onUpdate() {
+ if (appPermGroupLiveDatas.any { it.value.isStale }) {
+ return
+ }
+ var newGroups = false
+ for ((groupName, groupLiveData) in appPermGroupLiveDatas) {
+ val appPermGroup = groupLiveData.value
+ if (appPermGroup == null || groupName in permGroupsToSkip) {
+ if (appPermGroup == null) {
+ Log.w(LOG_TAG, "Group $packageName $groupName invalid")
+ }
+ groupStates[groupName to true]?.state = STATE_SKIPPED
+ groupStates[groupName to false]?.state = STATE_SKIPPED
+ continue
+ }
+
+ packageInfo = appPermGroup.packageInfo
+
+ val states = groupStates.filter { it.key.first == groupName }
+ if (states.isNotEmpty()) {
+ // some requests might have been granted, check for that
+ for ((key, state) in states) {
+ val allAffectedGranted = state.affectedPermissions.all { perm ->
+ appPermGroup.permissions[perm]?.isGrantedIncludingAppOp == true
+ }
+ if (allAffectedGranted) {
+ groupStates[key]!!.state = STATE_ALLOWED
+ }
+ }
+ } else {
+ newGroups = true
+ }
+ }
+
+ if (newGroups) {
+ groupStates = getRequiredGroupStates(
+ appPermGroupLiveDatas.mapNotNull { it.value.value })
+ }
+ getRequestInfosFromGroupStates()
+ }
+
+ private fun getRequestInfosFromGroupStates() {
+ val requestInfos = mutableListOf<RequestInfo>()
+ for ((key, groupState) in groupStates) {
+ val groupInfo = groupState.group.permGroupInfo
+ val (groupName, isBackground) = key
+ if (groupState.state != STATE_UNKNOWN) {
+ continue
+ }
+
+ val fgState = groupStates[groupName to false]
+ val bgState = groupStates[groupName to true]
+ var needFgPermissions = false
+ var needBgPermissions = false
+ var isFgUserSet = false
+ var isBgUserSet = false
+
+ if (fgState?.group?.foreground?.isGranted == false) {
+ needFgPermissions = true
+ isFgUserSet = fgState.group.foreground.isUserSet
+ }
+
+ if (bgState?.group?.background?.isGranted == false) {
+ needBgPermissions = true
+ isBgUserSet = bgState.group.background.isUserSet
+ }
+
+ val buttonVisibilities = MutableList(NEXT_BUTTON) { false }
+ buttonVisibilities[ALLOW_BUTTON] = true
+ buttonVisibilities[DENY_BUTTON] = true
+ buttonVisibilities[ALLOW_ONE_TIME_BUTTON] =
+ Utils.supportsOneTimeGrant(groupName)
+ var message = RequestMessage.FG_MESSAGE
+ // Whether or not to use the foreground, background, or no detail message.
+ // null ==
+ var detailMessage = RequestMessage.NO_MESSAGE
+
+ if (groupState.group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.R) {
+ if (isBackground || groupState.group.hasPermWithBackgroundMode) {
+ if (needFgPermissions) {
+ if (needBgPermissions) {
+ // Shouldn't be reached as background must be requested as a
+ // singleton
+ Log.e(LOG_TAG, "For R+ apps, background permissions must be " +
+ "requested after foreground permissions are already granted")
+ value = null
+ return
+ }
+ buttonVisibilities[ALLOW_BUTTON] = false
+ buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true
+ buttonVisibilities[DENY_BUTTON] = !isFgUserSet
+ buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
+ } else if (needBgPermissions) {
+ // Case: sdk >= R, BG/FG permission requesting BG only
+ requestInfos.add(RequestInfo(
+ groupInfo, sendToSettingsImmediately = true))
+ continue
+ } else {
+ // Not reached as the permissions should be auto-granted
+ value = null
+ return
+ }
+ } else {
+ // Case: sdk >= R, Requesting normal permission
+ buttonVisibilities[DENY_BUTTON] = !isFgUserSet
+ buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
+ }
+ } else {
+ if (isBackground || groupState.group.hasPermWithBackgroundMode) {
+ if (needFgPermissions) {
+ // Case: sdk < R, BG/FG permission requesting both or FG only
+ buttonVisibilities[ALLOW_BUTTON] = false
+ buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true
+ buttonVisibilities[DENY_BUTTON] = !isFgUserSet
+ buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
+ if (needBgPermissions) {
+ // Case: sdk < R, BG/FG permission requesting both
+ message = RequestMessage.BG_MESSAGE
+ detailMessage = RequestMessage.BG_MESSAGE
+ }
+ } else if (needBgPermissions) {
+ // Case: sdk < R, BG/FG permission requesting BG only
+ message = RequestMessage.UPGRADE_MESSAGE
+ detailMessage = RequestMessage.UPGRADE_MESSAGE
+ buttonVisibilities[ALLOW_BUTTON] = false
+ buttonVisibilities[DENY_BUTTON] = false
+ buttonVisibilities[ALLOW_ONE_TIME_BUTTON] = false
+ if (groupState.group.isOneTime) {
+ buttonVisibilities[NO_UPGRADE_OT_BUTTON] = !isBgUserSet
+ buttonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON] =
+ isBgUserSet
+ } else {
+ buttonVisibilities[NO_UPGRADE_BUTTON] = !isBgUserSet
+ buttonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON] =
+ isBgUserSet
+ }
+ } else {
+ // Not reached as the permissions should be auto-granted
+ value = null
+ return
+ }
+ } else {
+ // Case: sdk < R, Requesting normal permission
+ buttonVisibilities[DENY_BUTTON] = !isFgUserSet
+ buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet
+ }
+ }
+ buttonVisibilities[LINK_TO_SETTINGS] =
+ detailMessage != RequestMessage.NO_MESSAGE
+
+ requestInfos.add(RequestInfo(groupInfo, buttonVisibilities, message, detailMessage))
+ }
+ requestInfos.sortWith(Comparator { rhs, lhs ->
+ val rhsHasOneTime = rhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
+ val lhsHasOneTime = lhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
+ if (rhsHasOneTime && !lhsHasOneTime) {
+ -1
+ } else if (!rhsHasOneTime && lhsHasOneTime) {
+ 1
+ } else {
+ rhs.groupName.compareTo(lhs.groupName)
+ }
+ })
+
+ value = if (requestInfos.any { it.sendToSettingsImmediately } &&
+ requestInfos.size > 1) {
+ Log.e(LOG_TAG, "For R+ apps, background permissions must be requested " +
+ "individually")
+ null
+ } else {
+ requestInfos
+ }
+ }
+ }
+
+ /**
+ * Converts a list of LightAppPermGroups into a list of GroupStates
+ */
+ private fun getRequiredGroupStates(
+ groups: List<LightAppPermGroup>
+ ): MutableMap<Pair<String, Boolean>, GroupState> {
+ val groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>()
+ val filteredPermissions = unfilteredAffectedPermissions.filter { perm ->
+ val group = getGroupWithPerm(perm, groups)
+ group != null && isPermissionGrantableAndNotFixed(perm, group)
+ }
+ for (perm in filteredPermissions) {
+ val group = getGroupWithPerm(perm, groups)!!
+
+ val isBackground = perm in group.backgroundPermNames
+ val groupStateInfo = groupStates.getOrPut(group.permGroupName to isBackground) {
+ GroupState(group, isBackground)
+ }
+
+ var currGroupState = if (group.permGroupName in permGroupsToSkip) {
+ STATE_SKIPPED
+ } else {
+ groupStateInfo.state
+ }
+ if (storedState != null && currGroupState != STATE_UNKNOWN) {
+ currGroupState = storedState.getInt(getInstanceStateKey(group.permGroupName,
+ isBackground), STATE_UNKNOWN)
+ }
+
+ if (currGroupState == STATE_UNKNOWN) {
+ val otherGroupPermissions = filteredPermissions.filter { it in group.permissions }
+ currGroupState = getGroupState(perm, group, otherGroupPermissions)
+ }
+
+ if (currGroupState != STATE_UNKNOWN) {
+ groupStateInfo.state = currGroupState
+ }
+ // If we saved state, load it
+ groupStateInfo.affectedPermissions.add(perm)
+ }
+ return groupStates
+ }
+
+ /**
+ * Get the actually requested permissions when a permission is requested.
+ *
+ * >In some cases requesting to grant a single permission requires the system to grant
+ * additional permissions. E.g. before N-MR1 a single permission of a group caused the whole
+ * group to be granted. Another case are permissions that are split into two. For apps that
+ * target an SDK before the split, this method automatically adds the split off permission.
+ *
+ * @param perm The requested permission
+ *
+ * @return The actually requested permissions
+ */
+ private fun computeAffectedPermissions(
+ perm: String,
+ appPermissions: Map<String, List<String>>
+ ): List<String> {
+ val requestingAppTargetSDK = packageInfo.targetSdkVersion
+
+ // If a permission is split, all permissions the original permission is split into are
+ // affected
+ val extendedBySplitPerms = mutableListOf(perm)
+
+ val splitPerms = app.getSystemService(PermissionManager::class.java)!!.splitPermissions
+ for (splitPerm in splitPerms) {
+
+ if (requestingAppTargetSDK < splitPerm.targetSdk && perm == splitPerm.splitPermission) {
+ extendedBySplitPerms.addAll(splitPerm.newPermissions)
+ }
+ }
+
+ // For <= N_MR1 apps all permissions of the groups of the requested permissions are affected
+ if (requestingAppTargetSDK <= Build.VERSION_CODES.N_MR1) {
+ val extendedBySplitPermsAndGroup = mutableListOf<String>()
+
+ for (splitPerm in extendedBySplitPerms) {
+ val groups = appPermissions.filter { splitPerm in it.value }
+ if (groups.isEmpty()) {
+ continue
+ }
+
+ val permissionsInGroup = groups.values.first()
+ for (permissionInGroup in permissionsInGroup) {
+ extendedBySplitPermsAndGroup.add(permissionInGroup)
+ }
+ }
+
+ return extendedBySplitPermsAndGroup
+ } else {
+ return extendedBySplitPerms
+ }
+ }
+
+ private fun isPermissionGrantableAndNotFixed(perm: String, group: LightAppPermGroup): Boolean {
+
+ // If the permission is restricted it does not show in the UI and
+ // is not added to the group at all, so check that first.
+ if (perm in group.packageInfo.requestedPermissions && perm !in group.permissions) {
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION)
+ return false
+ }
+
+ val subGroup = if (perm in group.backgroundPermNames) {
+ group.background
+ } else {
+ group.foreground
+ }
+
+ val lightPermission = group.permissions[perm] ?: return false
+
+ if (!subGroup.isGrantable) {
+ reportRequestResult(perm, PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
+ // Skip showing groups that we know cannot be granted.
+ return false
+ } else if (subGroup.isUserFixed) {
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED)
+ return false
+ } else if (subGroup.isPolicyFixed && !subGroup.isGranted || lightPermission.isPolicyFixed) {
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED)
+ return false
+ }
+
+ return true
+ }
+
+ private fun getGroupState(
+ perm: String,
+ group: LightAppPermGroup,
+ otherGroupRequestedPermissions: List<String>
+ ): Int {
+ val policyState = getStateFromPolicy(perm, group)
+ if (policyState != STATE_UNKNOWN) {
+ return policyState
+ }
+
+ val isBackground = perm in group.backgroundPermNames
+
+ val hasForegroundRequest = otherGroupRequestedPermissions.any {
+ it !in group.backgroundPermNames
+ }
+
+ // Do not attempt to grant background access if foreground access is not either already
+ // granted or requested
+ if (isBackground && !group.foreground.isGranted && !hasForegroundRequest) {
+ Log.w(LOG_TAG, "Cannot grant $perm as the matching foreground permission is not " +
+ "already granted.")
+ val affectedPermissions = otherGroupRequestedPermissions.filter {
+ it in group.backgroundPermNames
+ }
+ reportRequestResult(affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
+ return STATE_SKIPPED
+ }
+
+ if (isBackground && group.background.isGranted ||
+ !isBackground && group.foreground.isGranted) {
+ if (isBackground) {
+ KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm))
+ } else {
+ KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm))
+ }
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to false,
+ FLAG_PERMISSION_USER_FIXED to false, filterPermissions = listOf(perm))
+
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED)
+ return if (storedState == null) {
+ STATE_SKIPPED
+ } else {
+ STATE_ALLOWED
+ }
+ }
+ return STATE_UNKNOWN
+ }
+
+ private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int {
+ val isBackground = perm in group.backgroundPermNames
+ var skipGroup = false
+ var state = STATE_UNKNOWN
+ when (permissionPolicy) {
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT -> {
+ if (isBackground) {
+ KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm))
+ } else {
+ KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm))
+ }
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
+ FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
+ filterPermissions = listOf(perm))
+ state = STATE_ALLOWED
+ skipGroup = true
+
+ getAutoGrantNotifier().onPermissionAutoGranted(perm)
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED)
+ }
+
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY -> {
+ if (group.permissions[perm]?.isPolicyFixed == false) {
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
+ FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
+ filterPermissions = listOf(perm))
+ }
+ state = STATE_DENIED
+ skipGroup = true
+
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED)
+ }
+ }
+ if (skipGroup && storedState == null) {
+ return STATE_SKIPPED
+ }
+ return state
+ }
+
+ /**
+ * Upon the user clicking a button, grant permissions, if applicable.
+ *
+ * @param groupName The name of the permission group which was changed
+ * @param result The choice the user made regarding the group.
+ */
+ fun onPermissionGrantResult(groupName: String?, result: Int) {
+ if (groupName == null) {
+ return
+ }
+ val foregroundGroupState = groupStates[groupName to false]
+ val backgroundGroupState = groupStates[groupName to true]
+ when (result) {
+ GrantPermissionsViewHandler.CANCELED -> {
+ if (foregroundGroupState != null) {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
+ }
+ if (backgroundGroupState != null) {
+ reportRequestResult(backgroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
+ }
+ return
+ }
+ GRANTED_ALWAYS -> {
+ if (foregroundGroupState != null) {
+ onPermissionGrantResultSingleState(foregroundGroupState,
+ granted = true, isOneTime = false, doNotAskAgain = false)
+ }
+ if (backgroundGroupState != null) {
+ onPermissionGrantResultSingleState(backgroundGroupState,
+ granted = true, isOneTime = false, doNotAskAgain = false)
+ }
+ }
+ GRANTED_FOREGROUND_ONLY -> {
+ if (foregroundGroupState != null) {
+ onPermissionGrantResultSingleState(foregroundGroupState,
+ granted = true, isOneTime = false, doNotAskAgain = false)
+ }
+ if (backgroundGroupState != null) {
+ onPermissionGrantResultSingleState(backgroundGroupState,
+ granted = false, isOneTime = false, doNotAskAgain = false)
+ }
+ }
+ GrantPermissionsViewHandler.GRANTED_ONE_TIME -> {
+ if (foregroundGroupState != null) {
+ onPermissionGrantResultSingleState(foregroundGroupState,
+ granted = true, isOneTime = true, doNotAskAgain = false)
+ }
+ if (backgroundGroupState != null) {
+ onPermissionGrantResultSingleState(backgroundGroupState,
+ granted = false, isOneTime = true, doNotAskAgain = false)
+ }
+ }
+ DENIED -> {
+ if (foregroundGroupState != null) {
+ onPermissionGrantResultSingleState(foregroundGroupState,
+ granted = false, isOneTime = false, doNotAskAgain = false)
+ }
+ if (backgroundGroupState != null) {
+ onPermissionGrantResultSingleState(backgroundGroupState, granted = false,
+ isOneTime = false, doNotAskAgain = false)
+ }
+ }
+ DENIED_DO_NOT_ASK_AGAIN -> {
+ if (foregroundGroupState != null) {
+ onPermissionGrantResultSingleState(foregroundGroupState,
+ granted = false, isOneTime = false, doNotAskAgain = true)
+ }
+ if (backgroundGroupState != null) {
+ onPermissionGrantResultSingleState(backgroundGroupState,
+ granted = false, isOneTime = false, doNotAskAgain = true)
+ }
+ }
+ }
+ }
+
+ private fun onPermissionGrantResultSingleState(
+ groupState: GroupState,
+ granted: Boolean,
+ isOneTime: Boolean,
+ doNotAskAgain: Boolean
+ ) {
+ if (groupState.state != STATE_UNKNOWN) {
+ // We already dealt with this group, don't re-grant/re-revoke
+ return
+ }
+ val result: Int
+ if (granted) {
+ result = if (isOneTime) {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
+ } else {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
+ }
+ if (groupState.isBackground) {
+ KotlinUtils.grantBackgroundRuntimePermissions(app, groupState.group,
+ groupState.affectedPermissions)
+ } else {
+ KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group,
+ groupState.affectedPermissions, isOneTime)
+ }
+ groupState.state = STATE_ALLOWED
+ } else {
+ if (groupState.isBackground) {
+ KotlinUtils.revokeBackgroundRuntimePermissions(app, groupState.group,
+ userFixed = doNotAskAgain, filterPermissions = groupState.affectedPermissions)
+ } else {
+ KotlinUtils.revokeForegroundRuntimePermissions(app, groupState.group,
+ userFixed = doNotAskAgain, filterPermissions = groupState.affectedPermissions)
+ }
+ result = if (doNotAskAgain) {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
+ } else {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
+ }
+ groupState.state = STATE_DENIED
+ }
+ reportRequestResult(groupState.affectedPermissions, result)
+ // group state has changed, reload liveData
+ requestInfosLiveData.updateIfActive()
+ }
+
+ private fun getGroupWithPerm(
+ perm: String,
+ groups: List<LightAppPermGroup>
+ ): LightAppPermGroup? {
+ val groupsWithPerm = groups.filter { perm in it.permissions }
+ if (groupsWithPerm.isEmpty()) {
+ return null
+ }
+ return groupsWithPerm.first()
+ }
+
+ /**
+ * An internal class which represents the state of a current AppPermissionGroup grant request.
+ */
+ internal class GroupState(
+ internal val group: LightAppPermGroup,
+ internal val isBackground: Boolean,
+ internal val affectedPermissions: MutableList<String> = mutableListOf(),
+ internal var state: Int = STATE_UNKNOWN
+ ) {
+ override fun toString(): String {
+ val stateStr: String = when (state) {
+ STATE_UNKNOWN -> "unknown"
+ STATE_ALLOWED -> "granted"
+ STATE_DENIED -> "denied"
+ else -> "skipped"
+ }
+ return "${group.permGroupName} $isBackground $stateStr $affectedPermissions"
+ }
+ }
+
+ private fun reportRequestResult(permissions: List<String>, result: Int) {
+ for (perm in permissions) {
+ reportRequestResult(perm, result)
+ }
+ }
+
+ /**
+ * Report the result of a grant of a permission.
+ *
+ * @param permission The permission that was granted or denied
+ * @param result The permission grant result
+ */
+ private fun reportRequestResult(permission: String, result: Int) {
+ val isImplicit = permission !in requestedPermissions
+
+ Log.v(LOG_TAG, "Permission grant result requestId=$sessionId " +
+ "callingUid=${packageInfo.uid} callingPackage=$packageName permission=$permission " +
+ "isImplicit=$isImplicit result=$result")
+
+ PermissionControllerStatsLog.write(
+ PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, sessionId,
+ packageInfo.uid, packageName, permission, isImplicit, result)
+ }
+
+ /**
+ * Save the group states of the view model, to allow for state restoration after lifecycle
+ * events
+ *
+ * @param outState The bundle in which to store state
+ */
+ fun saveInstanceState(outState: Bundle) {
+ for ((groupKey, groupState) in groupStates) {
+ val (groupName, isBackground) = groupKey
+ outState.putInt(getInstanceStateKey(groupName, isBackground), groupState.state)
+ }
+ }
+
+ /**
+ * Determine if the activity should return permission state to the caller
+ *
+ * @return whether or not state should be returned, or true if the calling PackageInfo has not
+ * been initialized (which it should always be)
+ */
+ fun shouldReturnPermissionState(): Boolean {
+ return if (packageInfoLiveData.isInitialized) {
+ packageInfo.targetSdkVersion >= Build.VERSION_CODES.M
+ } else {
+ // Should not be reached, as this method shouldn't be called before data is passed to
+ // the activity for the first time
+ true
+ }
+ }
+
+ /**
+ * Send the user directly to the AppPermissionFragment. Used for R+ apps.
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ fun sendDirectlyToSettings(activity: Activity, groupName: String) {
+ if (activityResultCallback == null) {
+ startAppPermissionFragment(activity, groupName)
+ activityResultCallback = Consumer { data ->
+ if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
+ // User didn't interact, count against rate limit
+ val group = groupStates[groupName to false]?.group
+ ?: groupStates[groupName to true]?.group ?: return@Consumer
+ if (group.background.isUserSet) {
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_FIXED to true,
+ filterPermissions = group.backgroundPermNames)
+ } else {
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to true,
+ filterPermissions = group.backgroundPermNames)
+ }
+ }
+
+ permGroupsToSkip.add(groupName)
+ // Update our liveData now that there is a new skipped group
+ requestInfosLiveData.updateIfActive()
+ }
+ }
+ }
+
+ /**
+ * Send the user to the AppPermissionFragment from a link. Used for Q- apps
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ fun sendToSettingsFromLink(activity: Activity, groupName: String) {
+ startAppPermissionFragment(activity, groupName)
+ activityResultCallback = Consumer { data ->
+ val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
+ if (returnGroupName != null) {
+ permGroupsToSkip.add(returnGroupName)
+ val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, -1)
+ logSettingsInteraction(returnGroupName, result)
+ requestInfosLiveData.updateIfActive()
+ }
+ }
+ }
+
+ private fun startAppPermissionFragment(activity: Activity, groupName: String) {
+ val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ .putExtra(Intent.EXTRA_USER, user)
+ .putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME,
+ GrantPermissionsActivity::class.java.name)
+ .putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+
+ private fun getInstanceStateKey(groupName: String, isBackground: Boolean): String {
+ return "${this::class.java.name}_${groupName}_$isBackground"
+ }
+
+ private fun logSettingsInteraction(groupName: String, result: Int) {
+ val foregroundGroupState = groupStates[groupName to false]
+ val backgroundGroupState = groupStates[groupName to true]
+ val deniedPrejudiceInSettings =
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS
+ when (result) {
+ GRANTED_ALWAYS -> {
+ if (foregroundGroupState != null) {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
+ }
+ if (backgroundGroupState != null) {
+ reportRequestResult(backgroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
+ }
+ }
+ GRANTED_FOREGROUND_ONLY -> {
+ if (foregroundGroupState != null) {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
+ }
+ if (backgroundGroupState != null) {
+ reportRequestResult(backgroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
+ }
+ }
+ DENIED -> {
+ if (foregroundGroupState != null) {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
+ }
+ if (backgroundGroupState != null) {
+ reportRequestResult(backgroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
+ }
+ }
+ DENIED_DO_NOT_ASK_AGAIN -> {
+ if (foregroundGroupState != null) {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ deniedPrejudiceInSettings)
+ }
+ if (backgroundGroupState != null) {
+ reportRequestResult(backgroundGroupState.affectedPermissions,
+ deniedPrejudiceInSettings)
+ }
+ }
+ }
+ }
+
+ /**
+ * Log all permission groups which were requested
+ */
+ fun logRequestedPermissionGroups() {
+ if (groupStates.isEmpty()) {
+ return
+ }
+ val groups = groupStates.map { it.value.group }
+ SafetyNetLogger.logPermissionsRequested(packageName, packageInfo.uid, groups)
+ }
+
+ /**
+ * Log information about the buttons which were shown and clicked by the user.
+ *
+ * @param groupName The name of the permission group which was interacted with
+ * @param clickedButton The button that was clicked by the user
+ * @param presentedButtons All buttons which were shown to the user
+ */
+ fun logClickedButtons(groupName: String?, clickedButton: Int, presentedButtons: Int) {
+ if (groupName == null) {
+ return
+ }
+ PermissionControllerStatsLog.write(GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS,
+ groupName, packageInfo.uid, packageName, presentedButtons, clickedButton, sessionId)
+ Log.v(LOG_TAG, "Logged buttons presented and clicked permissionGroupName=" +
+ "$groupName uid=${packageInfo.uid} package=$packageName presentedButtons=" +
+ "$presentedButtons clickedButton=$clickedButton sessionId=$sessionId")
+ }
+
+ /**
+ * Use the autoGrantNotifier to notify of auto-granted permissions.
+ */
+ fun autoGrantNotify() {
+ autoGrantNotifier?.notifyOfAutoGrantPermissions(true)
+ }
+
+ companion object {
+ private const val APP_PERMISSION_REQUEST_CODE = 1
+ private const val STATE_UNKNOWN = 0
+ private const val STATE_ALLOWED = 1
+ private const val STATE_DENIED = 2
+ private const val STATE_SKIPPED = 3
+
+ /**
+ * An enum that represents the type of message which should be shown- foreground,
+ * background, upgrade, or no message.
+ */
+ enum class RequestMessage(request: Int) {
+ FG_MESSAGE(0),
+ BG_MESSAGE(1),
+ UPGRADE_MESSAGE(2),
+ NO_MESSAGE(3)
+ }
+ }
+}
+
+/**
+ * Factory for an AppPermissionViewModel
+ *
+ * @param app The current application
+ * @param packageName The name of the package this ViewModel represents
+ */
+class GrantPermissionsViewModelFactory(
+ private val app: Application,
+ private val packageName: String,
+ private val requestedPermissions: Array<String>,
+ private val sessionId: Long,
+ private val savedState: Bundle?
+) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return GrantPermissionsViewModel(app, packageName, requestedPermissions.toList(), sessionId,
+ savedState) as T
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index 64068fe41..ebf23b099 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -46,6 +46,7 @@ import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
+import android.permission.PermissionManager
import android.text.TextUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
@@ -56,6 +57,7 @@ import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
import com.android.permissioncontroller.permission.model.livedatatypes.PermState
import com.android.permissioncontroller.permission.service.LocationAccessCheck
@@ -87,6 +89,20 @@ object KotlinUtils {
private const val KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"
/**
+ * Importance level to define the threshold for whether a package is in a state which resets the
+ * timer on its one-time permission session
+ */
+ private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER =
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+
+ /**
+ * Importance level to define the threshold for whether a package is in a state which keeps its
+ * one-time permission session alive after the timer ends
+ */
+ private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
+
+ /**
* Given a Map, and a List, determines which elements are in the list, but not the map, and
* vice versa. Used primarily for determining which liveDatas are already being watched, and
* which need to be removed or added
@@ -365,6 +381,50 @@ object KotlinUtils {
}
/**
+ * Set a list of flags for a set of permissions of a LightAppPermGroup
+ *
+ * @param app: The current application
+ * @param group: The LightAppPermGroup whose permission flags we wish to set
+ * @param flags: Pairs of <FlagInt, ShouldSetFlag>
+ * @param filterPermissions: A list of permissions to filter by. Only the filtered permissions
+ * will be set
+ *
+ * @return A new LightAppPermGroup with the flags set.
+ */
+ fun setGroupFlags(
+ app: Application,
+ group: LightAppPermGroup,
+ vararg flags: Pair<Int, Boolean>,
+ filterPermissions: List<String> = group.permissions.keys.toList()
+ ): LightAppPermGroup {
+ var flagMask = 0
+ var flagsToSet = 0
+ for ((flag, shouldSet) in flags) {
+ flagMask = flagMask or flag
+ if (shouldSet) {
+ flagsToSet = flagsToSet or flag
+ }
+ }
+
+ val permFlagFilter = flagsToSet or flagMask.inv()
+ val newPerms = mutableMapOf<String, LightPermission>()
+ for ((permName, perm) in group.permissions) {
+ if (permName !in filterPermissions) {
+ continue
+ }
+ val newFlags = perm.flags and permFlagFilter
+ if (newFlags != perm.flags) {
+ app.packageManager.updatePermissionFlags(permName, group.packageName,
+ group.userHandle, *flags)
+ }
+ newPerms[permName] = LightPermission(group.packageInfo, perm.permInfo,
+ perm.isGrantedIncludingAppOp, newFlags, perm.foregroundPerms)
+ }
+ return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
+ group.hasInstallToRuntimeSplit, group.specialLocationGrant)
+ }
+
+ /**
* Grant all foreground runtime permissions of a LightAppPermGroup
*
* <p>This also automatically grants all app ops for permissions that have app ops.
@@ -381,9 +441,10 @@ object KotlinUtils {
fun grantForegroundRuntimePermissions(
app: Application,
group: LightAppPermGroup,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ filterPermissions: List<String> = group.permissions.keys.toList(),
+ isOneTime: Boolean = false
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, false, filterPermissions)
+ return grantRuntimePermissions(app, group, false, isOneTime, filterPermissions)
}
/**
@@ -405,22 +466,24 @@ object KotlinUtils {
group: LightAppPermGroup,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, true, filterPermissions)
+ return grantRuntimePermissions(app, group, true, false, filterPermissions)
}
private fun grantRuntimePermissions(
app: Application,
group: LightAppPermGroup,
grantBackground: Boolean,
+ isOneTime: Boolean = false,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
+ val wasOneTime = group.isOneTime
val newPerms = group.permissions.toMutableMap()
var shouldKillForAnyPermission = false
for (permName in filterPermissions) {
val perm = group.permissions[permName] ?: continue
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == grantBackground) {
- val (newPerm, shouldKill) = grantRuntimePermission(app, perm, group)
+ val (newPerm, shouldKill) = grantRuntimePermission(app, perm, isOneTime, group)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -430,8 +493,15 @@ object KotlinUtils {
(app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
}
- return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
+ val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
group.hasInstallToRuntimeSplit, group.specialLocationGrant)
+ if (newGroup.isOneTime) {
+ app.getSystemService(PermissionManager::class.java)!!.startOneTimePermissionSession(
+ group.packageName, Utils.getOneTimePermissionsTimeout(),
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE)
+ }
+ return newGroup
}
/**
@@ -448,6 +518,7 @@ object KotlinUtils {
private fun grantRuntimePermission(
app: Application,
perm: LightPermission,
+ isOneTime: Boolean,
group: LightAppPermGroup
): Pair<LightPermission, Boolean> {
val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
@@ -497,9 +568,14 @@ object KotlinUtils {
// no longer has it fixed in a denied state.
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
+ newFlags = if (isOneTime) {
+ newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ } else {
+ newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ }
+
// If we newly grant background access to the fine location, double-guess the user some
// time later if this was really the right choice.
if (!perm.isGrantedIncludingAppOp && isGranted) {
@@ -586,6 +662,7 @@ object KotlinUtils {
oneTime: Boolean,
filterPermissions: List<String>
): LightAppPermGroup {
+ val wasOneTime = group.isOneTime
val newPerms = group.permissions.toMutableMap()
var shouldKillForAnyPermission = false
for (permName in filterPermissions) {
@@ -603,11 +680,51 @@ object KotlinUtils {
(app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
}
- return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
+
+ val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
group.hasInstallToRuntimeSplit, group.specialLocationGrant)
+
+ if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup)) {
+ app.getSystemService(PermissionManager::class.java)!!.stopOneTimePermissionSession(
+ group.packageName)
+ }
+ return newGroup
}
/**
+ * Determines if any permissions of a package are granted for one-time only
+ *
+ * @param app The current application
+ * @param packageInfo The packageInfo we wish to examine
+ * @param group Optional, the current app permission group we are examining
+ *
+ * @return true if any permission in the package is granted for one time, false otherwise
+ */
+ private fun anyPermsOfPackageOneTimeGranted(
+ app: Application,
+ packageInfo: LightPackageInfo,
+ group: LightAppPermGroup? = null
+ ): Boolean {
+ val user = group?.userHandle ?: UserHandle.getUserHandleForUid(packageInfo.uid)
+ if (group?.isOneTime == true) {
+ return true
+ }
+ for ((idx, permName) in packageInfo.requestedPermissions.withIndex()) {
+ if (permName in group?.permissions ?: emptyMap()) {
+ continue
+ }
+ val flags = app.packageManager.getPermissionFlags(permName, packageInfo.packageName,
+ user) and FLAG_PERMISSION_ONE_TIME
+ val granted = packageInfo.requestedPermissionsFlags[idx] ==
+ PackageManager.PERMISSION_GRANTED &&
+ (flags and FLAG_PERMISSION_REVOKED_COMPAT) == 0
+ if (granted && (flags and FLAG_PERMISSION_ONE_TIME) != 0) {
+ return true
+ }
+ }
+ return false
+ }
+ /**
* Revokes a single runtime permission.
*
* @param app The current application
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java
index ec2d261be..f0227cad5 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java
@@ -44,6 +44,14 @@ public final class SafetyNetLogger {
/* do nothing */
}
+ /**
+ * Log that permission groups have been requested for the purpose of safety net.
+ *
+ * <p>The groups might refer to different permission groups and different apps.
+ *
+ * @param packageInfo The info about the package for which permissions were requested
+ * @param groups The permission groups which were requested
+ */
public static void logPermissionsRequested(PackageInfo packageInfo,
List<AppPermissionGroup> groups) {
EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_REQUESTED,
@@ -52,6 +60,21 @@ public final class SafetyNetLogger {
}
/**
+ * Log that permission groups have been requested for the purpose of safety net.
+ *
+ * <p>The groups might refer to different permission groups and different apps.
+ *
+ * @param packageName The name of the package for which permissions were requested
+ * @param uid The uid of the package
+ * @param groups The permission groups which were requested
+ */
+ public static void logPermissionsRequested(String packageName, int uid,
+ List<LightAppPermGroup> groups) {
+ EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_REQUESTED, uid,
+ buildChangedPermissionForPackageMessageNew(packageName, groups));
+ }
+
+ /**
* Log that permission groups have been toggled for the purpose of safety net.
*
* <p>The groups might refer to different permission groups and different apps.
@@ -106,9 +129,10 @@ public final class SafetyNetLogger {
* background
*/
public static void logPermissionToggled(LightAppPermGroup group, boolean logOnlyBackground) {
+ StringBuilder builder = new StringBuilder();
+ buildChangedPermissionForGroup(group, logOnlyBackground, builder);
EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_TOGGLED,
- android.os.Process.myUid(), buildChangedPermissionForPackageMessage(group,
- logOnlyBackground));
+ android.os.Process.myUid(), builder.toString());
}
/**
@@ -122,9 +146,8 @@ public final class SafetyNetLogger {
logPermissionToggled(group, false);
}
- private static String buildChangedPermissionForPackageMessage(
- LightAppPermGroup group, boolean logOnlyBackground) {
- StringBuilder builder = new StringBuilder();
+ private static void buildChangedPermissionForGroup(LightAppPermGroup group,
+ boolean logOnlyBackground, StringBuilder builder) {
builder.append(group.getPackageInfo().getPackageName()).append(':');
@@ -142,8 +165,6 @@ public final class SafetyNetLogger {
builder.append(permission.isGrantedIncludingAppOp()).append('|');
builder.append(permission.getFlags());
}
-
- return builder.toString();
}
private static void buildChangedPermissionForGroup(AppPermissionGroup group,
@@ -162,6 +183,17 @@ public final class SafetyNetLogger {
}
}
+ private static String buildChangedPermissionForPackageMessageNew(String packageName,
+ List<LightAppPermGroup> groups) {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append(packageName).append(':');
+ for (LightAppPermGroup group: groups) {
+ buildChangedPermissionForGroup(group, false, builder);
+ }
+ return builder.toString();
+ }
+
private static String buildChangedPermissionForPackageMessage(String packageName,
List<AppPermissionGroup> groups) {
StringBuilder builder = new StringBuilder();
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index 65fdd590a..95a28c4f0 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -29,6 +29,8 @@ import static android.Manifest.permission_group.PHONE;
import static android.Manifest.permission_group.SENSORS;
import static android.Manifest.permission_group.SMS;
import static android.Manifest.permission_group.STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
@@ -43,6 +45,7 @@ import static android.os.UserHandle.myUserId;
import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
import android.Manifest;
+import android.app.AppOpsManager;
import android.app.Application;
import android.app.role.RoleManager;
import android.content.ActivityNotFoundException;
@@ -51,9 +54,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -62,6 +67,7 @@ import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
@@ -719,15 +725,23 @@ public final class Utils {
* Get the message shown to grant a permission group to an app.
*
* @param appLabel The label of the app
- * @param group the group to be granted
+ * @param packageName The package name of the app
+ * @param groupName The name of the permission group
* @param context A context to resolve resources
* @param requestRes The resource id of the grant request message
*
* @return The formatted message to be used as title when granting permissions
*/
- public static CharSequence getRequestMessage(CharSequence appLabel, AppPermissionGroup group,
- Context context, @StringRes int requestRes) {
- if (group.getName().equals(STORAGE) && !group.isNonIsolatedStorage()) {
+ public static CharSequence getRequestMessage(CharSequence appLabel, String packageName,
+ String groupName, Context context, @StringRes int requestRes) {
+
+ boolean isIsolatedStorage = false;
+ try {
+ isIsolatedStorage = !isNonIsolatedStorage(context, packageName);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ if (groupName.equals(STORAGE) && isIsolatedStorage) {
return Html.fromHtml(
String.format(context.getResources().getConfiguration().getLocales().get(0),
context.getString(R.string.permgrouprequest_storage_isolated),
@@ -737,7 +751,43 @@ public final class Utils {
}
return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel,
- group.getDescription()), 0);
+ loadGroupDescription(context, groupName, context.getPackageManager())), 0);
+ }
+
+ private static CharSequence loadGroupDescription(Context context, String groupName,
+ @NonNull PackageManager packageManager) {
+ PackageItemInfo groupInfo = getGroupInfo(groupName, context);
+ CharSequence description = null;
+ if (groupInfo instanceof PermissionGroupInfo) {
+ description = ((PermissionGroupInfo) groupInfo).loadDescription(packageManager);
+ } else if (groupInfo instanceof PermissionInfo) {
+ description = ((PermissionInfo) groupInfo).loadDescription(packageManager);
+ }
+
+ if (description == null || description.length() <= 0) {
+ description = context.getString(R.string.default_permission_description);
+ }
+
+ return description;
+ }
+
+ /**
+ * Whether or not the given package has non-isolated storage permissions
+ * @param context The current context
+ * @param packageName The package name to check
+ * @return True if the package has access to non-isolated storage, false otherwise
+ * @throws NameNotFoundException
+ */
+ public static boolean isNonIsolatedStorage(@NonNull Context context,
+ @NonNull String packageName) throws NameNotFoundException {
+ PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
+ AppOpsManager manager = context.getSystemService(AppOpsManager.class);
+
+
+ return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P
+ || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R
+ && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
+ packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
}
/**
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/permission/utils/GrantRevokeTests.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/permission/utils/GrantRevokeTests.kt
index a43f25cfd..fa63fe969 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/permission/utils/GrantRevokeTests.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/permission/utils/GrantRevokeTests.kt
@@ -39,6 +39,7 @@ import android.content.pm.PermissionInfo.PROTECTION_FLAG_INSTANT
import android.content.pm.PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY
import android.os.Build
import android.os.UserHandle
+import android.permission.PermissionManager
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo
@@ -120,6 +121,9 @@ class GrantRevokeTests {
`when`(app.getSystemService(ActivityManager::class.java)).thenReturn(
mock(ActivityManager::class.java))
+
+ `when`(app.getSystemService(PermissionManager::class.java)).thenReturn(
+ mock(PermissionManager::class.java))
}
/**
@@ -742,7 +746,7 @@ class GrantRevokeTests {
fun revokeTwoPermTest() {
val pkg = createMockPackage(mapOf(FG_PERM_NAME to true, FG_PERM_2_NAME to true))
val perms = mutableMapOf<String, LightPermission>()
- perms[FG_PERM_NAME] = createMockPerm(pkg,FG_PERM_NAME)
+ perms[FG_PERM_NAME] = createMockPerm(pkg, FG_PERM_NAME)
perms[FG_PERM_2_NAME] = createMockPerm(pkg, FG_PERM_2_NAME)
val group = createMockGroup(pkg, perms)
resetMockAppState()