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
     }