Refactor autorevoke data model for hibernation 3/3
Create two distinct live data objects depending on whether hibernation
is enabled or not. When enabled, the live data used for the view model
is unused apps that are also hibernated. When disabled, it uses unused
apps that are auto-revoked.
Bug: 181172051
Test: atest AutoRevokeTest
Test: grant runtime permission, run job, click notification and see that
the UX is the same
Test: add app to allowlist, run job, app is not auto-revoked
Change-Id: I1d39e3429cccc2c627d0b64e0a19fa3f65ab9d25
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
index c9f54ca..62d6387 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
@@ -72,9 +72,9 @@
import com.android.permissioncontroller.permission.data.HasIntentAction
import com.android.permissioncontroller.permission.data.ServiceLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
-import com.android.permissioncontroller.permission.data.UnusedAutoRevokedPackagesLiveData
import com.android.permissioncontroller.permission.data.UsageStatsLiveData
import com.android.permissioncontroller.permission.data.get
+import com.android.permissioncontroller.permission.data.getUnusedPackages
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.service.revokeAppPermissions
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
@@ -513,7 +513,7 @@
notificationManager.notify(HibernationJobService::class.java.simpleName,
Constants.AUTO_REVOKE_NOTIFICATION_ID, b.build())
// Preload the unused packages
- UnusedAutoRevokedPackagesLiveData.getInitializedValue()
+ getUnusedPackages().getInitializedValue()
}
override fun onStopJob(params: JobParameters?): Boolean {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokedPackagesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokedPackagesLiveData.kt
index 6c6fb0f..eb5dee2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokedPackagesLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokedPackagesLiveData.kt
@@ -21,9 +21,6 @@
import android.os.Build
import android.os.UserHandle
import android.util.Log
-import com.android.permissioncontroller.hibernation.getUnusedThresholdMs
-import com.android.permissioncontroller.permission.data.AutoRevokedPackagesLiveData.addSource
-import com.android.permissioncontroller.permission.data.UnusedAutoRevokedPackagesLiveData.addSource
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.Utils
import kotlinx.coroutines.Dispatchers.Main
@@ -40,7 +37,7 @@
object AutoRevokedPackagesLiveData
: SmartAsyncMediatorLiveData<Map<Pair<String, UserHandle>, Set<String>>>() {
- private val LOG_TAG = UnusedAutoRevokedPackagesLiveData::class.java.simpleName
+ private val LOG_TAG = AutoRevokedPackagesLiveData::class.java.simpleName
init {
addSource(AllPackageInfosLiveData) {
@@ -154,54 +151,20 @@
}
}
-/**
- * Gets all Auto Revoked packages that have not been opened in a few months. This will let us remove
- * used apps from the Auto Revoke screen.
- *
- * ```(packageName, user) -> [groupName]```
- */
-object UnusedAutoRevokedPackagesLiveData
- : SmartUpdateMediatorLiveData<Map<Pair<String, UserHandle>, Set<String>>>() {
-
- private val LOG_TAG = UnusedAutoRevokedPackagesLiveData::class.java.simpleName
-
- private val unusedThreshold = getUnusedThresholdMs()
- private val usageStatsLiveData = UsageStatsLiveData[unusedThreshold]
-
- init {
- addSource(usageStatsLiveData) {
- update()
- }
- addSource(AutoRevokedPackagesLiveData) {
- update()
- }
- }
-
- override fun onUpdate() {
- if (!usageStatsLiveData.isInitialized || !AutoRevokedPackagesLiveData.isInitialized) {
- return
- }
-
- val autoRevokedPackages = AutoRevokedPackagesLiveData.value!!
-
- val unusedPackages = mutableMapOf<Pair<String, UserHandle>, Set<String>>()
- for ((userPackage, perms) in autoRevokedPackages) {
- unusedPackages[userPackage] = perms.toSet()
- }
-
- val now = System.currentTimeMillis()
- for ((user, stats) in usageStatsLiveData.value!!) {
- for (stat in stats) {
- val userPackage = stat.packageName to user
- if (userPackage in autoRevokedPackages &&
- (now - stat.lastTimeVisible) < unusedThreshold) {
- unusedPackages.remove(userPackage)
- }
+private val autoRevokedPackagesSetLiveData =
+ object : SmartUpdateMediatorLiveData<Set<Pair<String, UserHandle>>>() {
+ init {
+ addSource(AutoRevokedPackagesLiveData) {
+ update()
}
}
- Log.i(LOG_TAG, "onUpdate() -> $unusedPackages")
-
- value = unusedPackages
+ override fun onUpdate() {
+ if (!AutoRevokedPackagesLiveData.isInitialized) {
+ return
+ }
+ value = AutoRevokedPackagesLiveData.value!!.keys
+ }
}
-}
\ No newline at end of file
+
+val unusedAutoRevokePackagesLiveData = UnusedPackagesLiveData(autoRevokedPackagesSetLiveData)
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/HibernatedPackagesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/HibernatedPackagesLiveData.kt
new file mode 100644
index 0000000..7cd578b
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/HibernatedPackagesLiveData.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data
+
+import android.apphibernation.AppHibernationManager
+import android.content.Context.APP_HIBERNATION_SERVICE
+import android.os.UserHandle
+import android.util.Log
+import com.android.permissioncontroller.DumpableLog
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.utils.Utils.getUserContext
+import kotlinx.coroutines.Job
+
+/**
+ * Tracks which packages have been hibernated.
+ */
+object HibernatedPackagesLiveData
+ : SmartAsyncMediatorLiveData<Set<Pair<String, UserHandle>>>() {
+ private val LOG_TAG = HibernatedPackagesLiveData::class.java.simpleName
+
+ init {
+ addSource(AllPackageInfosLiveData) {
+ update()
+ }
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ if (job.isCancelled) {
+ return
+ }
+ if (!AllPackageInfosLiveData.isInitialized) {
+ return
+ }
+ val allPackages = AllPackageInfosLiveData.value!!
+ val hibernatingPackages = mutableSetOf<Pair<String, UserHandle>>()
+ for ((user, pkgs) in allPackages) {
+ val userContext = getUserContext(PermissionControllerApplication.get(), user)
+ val hibernationManager =
+ userContext.getSystemService(APP_HIBERNATION_SERVICE) as AppHibernationManager
+ for (pkg in pkgs) {
+ try {
+ if (hibernationManager.isHibernatingForUser(pkg.packageName)) {
+ hibernatingPackages.add(pkg.packageName to user)
+ }
+ } catch (e: Exception) {
+ DumpableLog.e(LOG_TAG,
+ "Failed to get hibernation state of package: ${pkg.packageName}")
+ }
+ }
+ }
+
+ Log.i(LOG_TAG, "postValue: $hibernatingPackages")
+
+ postValue(hibernatingPackages)
+ }
+}
+
+val unusedHibernatedPackagesLiveData = UnusedPackagesLiveData(HibernatedPackagesLiveData)
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/UnusedPackagesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/UnusedPackagesLiveData.kt
new file mode 100644
index 0000000..aa6d67a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/UnusedPackagesLiveData.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data
+
+import android.os.UserHandle
+import android.util.ArraySet
+import android.util.Log
+import com.android.permissioncontroller.hibernation.getUnusedThresholdMs
+import com.android.permissioncontroller.hibernation.isHibernationEnabled
+
+/**
+ * Gets all unused packages from an existing live data that have not been opened in a few months
+ * and the permission groups that have been revoked for them, if any. This will let us removed used
+ * apps from the Unused Apps screen.
+ *
+ * @param sourceLiveData the live data for packages to base this list of unused apps on
+ * ```(packageName, user) -> [groupName]```
+ */
+class UnusedPackagesLiveData(
+ private val sourceLiveData: SmartUpdateMediatorLiveData<Set<Pair<String, UserHandle>>>
+) : SmartUpdateMediatorLiveData<Map<Pair<String, UserHandle>, Set<String>>>() {
+
+ private val LOG_TAG = UnusedPackagesLiveData::class.java.simpleName
+
+ private val unusedThreshold = getUnusedThresholdMs()
+ private val usageStatsLiveData = UsageStatsLiveData[unusedThreshold]
+
+ init {
+ addSource(usageStatsLiveData) {
+ update()
+ }
+ addSource(AutoRevokedPackagesLiveData) {
+ update()
+ }
+ addSource(sourceLiveData) {
+ update()
+ }
+ }
+
+ override fun onUpdate() {
+ if (!usageStatsLiveData.isInitialized ||
+ !AutoRevokedPackagesLiveData.isInitialized ||
+ !sourceLiveData.isInitialized) {
+ return
+ }
+
+ val sourcePackages = sourceLiveData.value!!
+ val autoRevokedPackages = AutoRevokedPackagesLiveData.value!!
+
+ val unusedPackages = mutableMapOf<Pair<String, UserHandle>, Set<String>>()
+ for (userPackage in sourcePackages) {
+ val perms = autoRevokedPackages[userPackage] ?: ArraySet()
+ unusedPackages[userPackage] = perms.toSet()
+ }
+
+ val now = System.currentTimeMillis()
+ for ((user, stats) in usageStatsLiveData.value!!) {
+ for (stat in stats) {
+ val userPackage = stat.packageName to user
+ if (userPackage in autoRevokedPackages &&
+ (now - stat.lastTimeVisible) < unusedThreshold) {
+ unusedPackages.remove(userPackage)
+ }
+ }
+ }
+
+ Log.i(LOG_TAG, "onUpdate() -> $unusedPackages")
+
+ value = unusedPackages
+ }
+}
+
+fun getUnusedPackages(): UnusedPackagesLiveData {
+ return if (isHibernationEnabled()) {
+ unusedHibernatedPackagesLiveData
+ } else {
+ unusedAutoRevokePackagesLiveData
+ }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AutoRevokeViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AutoRevokeViewModel.kt
index e72cf43..faa5ec3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AutoRevokeViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AutoRevokeViewModel.kt
@@ -23,22 +23,22 @@
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
+import android.util.Log
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
-import android.util.Log
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.AUTO_REVOKED_APP_INTERACTION
import com.android.permissioncontroller.PermissionControllerStatsLog.AUTO_REVOKED_APP_INTERACTION__ACTION__REMOVE
import com.android.permissioncontroller.PermissionControllerStatsLog.AUTO_REVOKE_FRAGMENT_APP_VIEWED
import com.android.permissioncontroller.PermissionControllerStatsLog.AUTO_REVOKE_FRAGMENT_APP_VIEWED__AGE__NEWER_BUCKET
import com.android.permissioncontroller.PermissionControllerStatsLog.AUTO_REVOKE_FRAGMENT_APP_VIEWED__AGE__OLDER_BUCKET
-import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
-import com.android.permissioncontroller.permission.data.UnusedAutoRevokedPackagesLiveData
import com.android.permissioncontroller.permission.data.UsageStatsLiveData
+import com.android.permissioncontroller.permission.data.getUnusedPackages
import com.android.permissioncontroller.permission.utils.IPC
+import com.android.permissioncontroller.permission.utils.Utils
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@@ -81,7 +81,7 @@
private val usageStatsLiveData = UsageStatsLiveData[SIX_MONTHS_MILLIS]
init {
- addSource(UnusedAutoRevokedPackagesLiveData) {
+ addSource(getUnusedPackages()) {
onUpdate()
}
@@ -95,12 +95,12 @@
}
override suspend fun loadDataAndPostValue(job: Job) {
- if (!UnusedAutoRevokedPackagesLiveData.isInitialized ||
+ if (!getUnusedPackages().isInitialized ||
!usageStatsLiveData.isInitialized || !AllPackageInfosLiveData.isInitialized) {
return
}
- val unusedApps = UnusedAutoRevokedPackagesLiveData.value!!
+ val unusedApps = getUnusedPackages().value!!
Log.i(LOG_TAG, "Unused apps: $unusedApps")
val overSixMonthApps = unusedApps.keys.toMutableSet()
val categorizedApps = mutableMapOf<Months, MutableList<UnusedPackageInfo>>()
@@ -163,7 +163,7 @@
}
fun areUnusedPackagesLoaded(): Boolean {
- return UnusedAutoRevokedPackagesLiveData.isInitialized
+ return getUnusedPackages().isInitialized
}
fun navigateToAppInfo(packageName: String, user: UserHandle, sessionId: Long) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
index 1ca3e63..4f1bd23 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
@@ -27,7 +27,7 @@
import com.android.permissioncontroller.permission.data.PermGroupsPackagesUiInfoLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.StandardPermGroupNamesLiveData
-import com.android.permissioncontroller.permission.data.UnusedAutoRevokedPackagesLiveData
+import com.android.permissioncontroller.permission.data.unusedAutoRevokePackagesLiveData
import com.android.permissioncontroller.permission.utils.navigateSafe
/**
@@ -44,7 +44,7 @@
val uiDataLiveData = PermGroupsPackagesUiInfoLiveData(app,
StandardPermGroupNamesLiveData)
val numCustomPermGroups = NumCustomPermGroupsWithPackagesLiveData()
- val numAutoRevoked = Transformations.map(UnusedAutoRevokedPackagesLiveData) {
+ val numAutoRevoked = Transformations.map(unusedAutoRevokePackagesLiveData) {
it?.size ?: 0
}