Fix several issues with Storage permission UI
Ensure UserPackageInfos live data updates on permission changes, ensure
that UI correctly handles case where app may have special app access,
but not permission, ensure special app access is calculated correctly,
kills app on app op change.
Fixes: 160166191
Test: atest CtsPermissionTestCases CtsPermission3TestCases
Change-Id: Ie194f41d11da3316350e2715049c55d596f45b19
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
index 99bddff..531600e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
@@ -16,10 +16,12 @@
package com.android.permissioncontroller.permission.data
-import android.Manifest
+import android.Manifest.permission.MANAGE_EXTERNAL_STORAGE
import android.Manifest.permission_group.STORAGE
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_FOREGROUND
import android.app.AppOpsManager.OPSTR_LEGACY_STORAGE
import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE
import android.app.Application
@@ -64,7 +66,7 @@
for ((user, packageInfoList) in AllPackageInfosLiveData.value ?: emptyMap()) {
val userPackages = packageInfoList.filter {
storagePackages.contains(it.packageName to user) ||
- it.requestedPermissions.contains(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ it.requestedPermissions.contains(MANAGE_EXTERNAL_STORAGE)
}
for (packageInfo in userPackages) {
@@ -80,10 +82,12 @@
isLegacy = true, isGranted = true))
continue
}
- if (packageInfo.requestedPermissions.contains(
- Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
- val granted = appOpsManager.unsafeCheckOpNoThrow(OPSTR_MANAGE_EXTERNAL_STORAGE,
- packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED
+ if (MANAGE_EXTERNAL_STORAGE in packageInfo.requestedPermissions) {
+ val mode = appOpsManager.unsafeCheckOpNoThrow(OPSTR_MANAGE_EXTERNAL_STORAGE,
+ packageInfo.uid, packageInfo.packageName)
+ val granted = mode == MODE_ALLOWED || mode == MODE_FOREGROUND ||
+ (mode == MODE_DEFAULT &&
+ MANAGE_EXTERNAL_STORAGE in packageInfo.grantedPermissions)
fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user,
isLegacy = false, isGranted = granted))
}
@@ -97,4 +101,12 @@
super.onActive()
updateAsync()
}
+
+ /**
+ * Recalculate the LiveData
+ * TODO ntmyren: Make livedata properly observe app ops
+ */
+ fun recalculate() {
+ updateAsync()
+ }
}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt
index 437b0e7..4c8039e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt
@@ -118,7 +118,8 @@
registeredUid = uid
PermissionListenerMultiplexer.addCallback(it, this)
}
- if (userPackagesLiveData.hasActiveObservers() && !watchingUserPackagesLiveData) {
+ if (userPackagesLiveData.hasActiveObservers() && !watchingUserPackagesLiveData &&
+ !userPackagesLiveData.permChangeStale) {
watchingUserPackagesLiveData = true
addSource(userPackagesLiveData, userPackageInfosObserver)
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/UserPackageInfosLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/UserPackageInfosLiveData.kt
index 7016164..caeb0fa 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/UserPackageInfosLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/UserPackageInfosLiveData.kt
@@ -35,12 +35,39 @@
private val app: Application,
private val user: UserHandle
) : SmartAsyncMediatorLiveData<@kotlin.jvm.JvmSuppressWildcards List<LightPackageInfo>>(),
- PackageBroadcastReceiver.PackageBroadcastListener {
+ PackageBroadcastReceiver.PackageBroadcastListener,
+ PermissionListenerMultiplexer.PermissionChangeCallback {
+
+ /**
+ * Whether or not the permissions in this liveData are out of date
+ */
+ var permChangeStale = false
override fun onPackageUpdate(packageName: String) {
updateAsync()
}
+ // TODO ntmyren: replace with correctly updating
+ override fun onPermissionChange() {
+ permChangeStale = true
+ for (packageInfo in value ?: emptyList()) {
+ PermissionListenerMultiplexer.removeCallback(packageInfo.uid, this)
+ }
+ }
+
+ override fun setValue(newValue: List<LightPackageInfo>?) {
+ if (newValue != value) {
+ for (packageInfo in value ?: emptyList()) {
+ PermissionListenerMultiplexer.removeCallback(packageInfo.uid, this)
+ }
+ for (packageInfo in newValue ?: emptyList()) {
+ PermissionListenerMultiplexer.addCallback(packageInfo.uid, this)
+ }
+ }
+ super.setValue(newValue)
+ permChangeStale = false
+ }
+
/**
* Get all of the packages in the system, organized by user.
*/
@@ -53,6 +80,7 @@
"${user.identifier}")
val packageInfos = app.applicationContext.packageManager
.getInstalledPackagesAsUser(GET_PERMISSIONS or MATCH_ALL, user.identifier)
+
postValue(packageInfos.map { packageInfo -> LightPackageInfo(packageInfo) })
}
@@ -60,12 +88,21 @@
super.onActive()
PackageBroadcastReceiver.addAllCallback(this)
+
+ for (packageInfo in value ?: emptyList()) {
+ PermissionListenerMultiplexer.addCallback(packageInfo.uid, this)
+ }
+
updateAsync()
}
override fun onInactive() {
super.onInactive()
+ for (packageInfo in value ?: emptyList()) {
+ PermissionListenerMultiplexer.removeCallback(packageInfo.uid, this)
+ }
+
PackageBroadcastReceiver.removeAllCallback(this)
}
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 13d111e..6a7002d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
@@ -359,6 +359,11 @@
setResult(DENIED);
});
mDenyButton.setOnClickListener((v) -> {
+
+ if (mViewModel.getFullStorageStateLiveData().getValue() != null
+ && !mViewModel.getFullStorageStateLiveData().getValue().isLegacy()) {
+ mViewModel.setAllFilesAccess(false);
+ }
mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_BOTH,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY);
setResult(DENIED_DO_NOT_ASK_AGAIN);
@@ -421,12 +426,8 @@
return;
}
- if (storageState.isGranted()) {
- textView.setText(R.string.app_permission_footer_special_file_access);
- textView.setVisibility(View.VISIBLE);
- } else {
- textView.setVisibility(View.GONE);
- }
+ textView.setText(R.string.app_permission_footer_special_file_access);
+ textView.setVisibility(View.VISIBLE);
}
private void setResult(@GrantPermissionsViewHandler.Result int result) {
@@ -532,7 +533,7 @@
private static final String KEY = ConfirmDialog.class.getName() + ".arg.key";
private static final String BUTTON = ConfirmDialog.class.getName() + ".arg.button";
private static final String ONE_TIME = ConfirmDialog.class.getName() + ".arg.onetime";
-
+ private static int sCode = APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AppPermissionFragment fragment = (AppPermissionFragment) getParentFragment();
@@ -550,6 +551,8 @@
(DialogInterface dialog, int which) -> {
if (isGrantFileAccess) {
fragment.mViewModel.setAllFilesAccess(true);
+ fragment.mViewModel.requestChange(false, fragment,
+ fragment, ChangeRequest.GRANT_BOTH, sCode);
} else {
fragment.mViewModel.onDenyAnyWay((ChangeRequest)
getArguments().getSerializable(CHANGE_REQUEST),
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
index c192166..b73b426 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
@@ -131,8 +131,6 @@
setLoading(true /* loading */, false /* animate */);
}
}, SHOW_LOAD_DELAY_MS);
- } else if (mViewModel.getCategorizedAppsLiveData().getValue() != null) {
- onPackagesLoaded(mViewModel.getCategorizedAppsLiveData().getValue());
}
setHasOptionsMenu(true);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
index 0642495..46af48a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
@@ -19,7 +19,7 @@
import android.Manifest
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_IGNORED
+import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE
import android.app.Application
import android.content.Intent
@@ -33,7 +33,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
-import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_VIEWED
@@ -343,6 +342,7 @@
allowedAllFilesState.isShown = true
if (storageState.isGranted) {
allowedAllFilesState.isChecked = true
+ deniedState.isChecked = false
}
} else {
allowedAllFilesState.isEnabled = false
@@ -576,6 +576,10 @@
}
logPermissionChanges(oldGroup, newGroup, buttonClicked)
+
+ fullStorageStateLiveData.value?.let {
+ FullStoragePermissionAppsLiveData.recalculate()
+ }
}
/**
@@ -620,18 +624,30 @@
if (hasDefaultPermissions || !group.supportsRuntimePerms) {
hasConfirmedRevoke = true
}
+
+ fullStorageStateLiveData.value?.let {
+ FullStoragePermissionAppsLiveData.recalculate()
+ }
}
+ /**
+ * Set the All Files access for this app
+ *
+ * @param granted Whether to grant or revoke access
+ */
fun setAllFilesAccess(granted: Boolean) {
- val aom =
- PermissionControllerApplication.get().getSystemService(AppOpsManager::class.java)!!
+ val aom = app.getSystemService(AppOpsManager::class.java)!!
val uid = lightAppPermGroup?.packageInfo?.uid ?: return
val mode = if (granted) {
MODE_ALLOWED
} else {
- MODE_IGNORED
+ MODE_ERRORED
}
- aom.setUidMode(OPSTR_MANAGE_EXTERNAL_STORAGE, uid, mode)
+ val fullStorageGrant = fullStorageStateLiveData.value?.isGranted
+ if (fullStorageGrant != null && fullStorageGrant != granted) {
+ aom.setUidMode(OPSTR_MANAGE_EXTERNAL_STORAGE, uid, mode)
+ FullStoragePermissionAppsLiveData.recalculate()
+ }
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
index 3772cd7..c7a13db 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
@@ -88,15 +88,6 @@
init {
var fullStorageLiveData: FullStoragePermissionAppsLiveData? = null
- addSource(packagesUiInfoLiveData) {
- if (fullStorageLiveData == null || fullStorageLiveData?.isInitialized == true)
- update()
- }
- addSource(shouldShowSystemLiveData) {
- if (fullStorageLiveData == null || fullStorageLiveData?.isInitialized == true)
- update()
- }
-
// If this is the Storage group, observe a FullStoragePermissionAppsLiveData, update
// the packagesWithFullFileAccess list, and call update to populate the subtitles.
if (groupName == Manifest.permission_group.STORAGE) {
@@ -104,7 +95,6 @@
addSource(FullStoragePermissionAppsLiveData) { fullAccessPackages ->
if (fullAccessPackages != packagesWithFullFileAccess) {
packagesWithFullFileAccess = fullAccessPackages.filter { it.isGranted }
- ?: emptyList()
if (packagesUiInfoLiveData.isInitialized) {
update()
}
@@ -112,8 +102,19 @@
}
}
+ addSource(packagesUiInfoLiveData) {
+ if (fullStorageLiveData == null || fullStorageLiveData.isInitialized)
+ update()
+ }
+ addSource(shouldShowSystemLiveData) {
+ if (fullStorageLiveData == null || fullStorageLiveData.isInitialized)
+ update()
+ }
+
if ((fullStorageLiveData == null || fullStorageLiveData.isInitialized) &&
packagesUiInfoLiveData.isInitialized) {
+ packagesWithFullFileAccess = fullStorageLiveData?.value?.filter { it.isGranted }
+ ?: emptyList()
update()
}
}
@@ -155,13 +156,19 @@
showAlwaysAllowedString = true
}
- val category = when (uiInfo.permGrantState) {
+ var category = when (uiInfo.permGrantState) {
PermGrantState.PERMS_ALLOWED -> Category.ALLOWED
PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> Category.ALLOWED_FOREGROUND
PermGrantState.PERMS_ALLOWED_ALWAYS -> Category.ALLOWED
PermGrantState.PERMS_DENIED -> Category.DENIED
PermGrantState.PERMS_ASK -> Category.ASK
}
+
+ if (groupName == Manifest.permission_group.STORAGE &&
+ packagesWithFullFileAccess.any { !it.isLegacy && it.isGranted &&
+ it.packageName to it.user == packageUserPair }) {
+ category = Category.ALLOWED
+ }
categoryMap[category]!!.add(packageUserPair)
}
showAllowAlwaysStringLiveData.value = showAlwaysAllowedString