1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.permissioncontroller.hibernation.v31
18 
19 import android.app.usage.UsageStatsManager
20 import android.apphibernation.AppHibernationManager
21 import android.content.Context
22 import android.content.Context.APP_HIBERNATION_SERVICE
23 import android.content.Context.USAGE_STATS_SERVICE
24 import android.os.Build
25 import android.os.UserHandle
26 import androidx.annotation.RequiresApi
27 import com.android.permissioncontroller.DumpableLog
28 import com.android.permissioncontroller.permission.data.HibernatedPackagesLiveData
29 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
30 
31 /** Hibernation controller that handles modifying hibernation state. */
32 @RequiresApi(Build.VERSION_CODES.S)
33 class HibernationController(
34     private val context: Context,
35     private val unusedThreshold: Long,
36     private val targetsPreS: Boolean
37 ) {
38 
39     companion object {
40         private const val LOG_TAG = "HibernationController"
41         private const val DEBUG_HIBERNATION = true
42     }
43 
44     /**
45      * Hibernates the apps provided for each user.
46      *
47      * @param apps map of each user to a list of packages that should be hibernated for the user
48      * @return list of apps that were successfully hibernated
49      */
hibernateAppsnull50     fun hibernateApps(
51         apps: Map<UserHandle, List<LightPackageInfo>>
52     ): Set<Pair<String, UserHandle>> {
53         val hibernatedApps = mutableSetOf<Pair<String, UserHandle>>()
54         for ((user, userApps) in apps) {
55             val userContext = context.createContextAsUser(user, 0 /* flags */)
56             val hibernationManager =
57                 userContext.getSystemService(APP_HIBERNATION_SERVICE) as AppHibernationManager
58             for (pkg in userApps) {
59                 try {
60                     if (hibernationManager.isHibernatingForUser(pkg.packageName)) {
61                         continue
62                     }
63                     if (!targetsPreS && pkg.targetSdkVersion < Build.VERSION_CODES.S) {
64                         // Only apps targeting S or above can be truly hibernated.
65                         continue
66                     }
67                     hibernationManager.setHibernatingForUser(pkg.packageName, true)
68                     hibernatedApps.add(pkg.packageName to user)
69                 } catch (e: Exception) {
70                     DumpableLog.e(LOG_TAG, "Failed to hibernate package: ${pkg.packageName}", e)
71                 }
72             }
73         }
74 
75         // Globally hibernate any of the hibernated apps that are unused by any user
76         val usageStatsManager = context.getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager
77         val hibernationManager =
78             context.getSystemService(APP_HIBERNATION_SERVICE) as AppHibernationManager
79         val globallyHibernatedApps = mutableSetOf<String>()
80         for ((pkgName, _) in hibernatedApps) {
81             if (
82                 globallyHibernatedApps.contains(pkgName) ||
83                     hibernationManager.isHibernatingGlobally(pkgName)
84             ) {
85                 continue
86             }
87 
88             val now = System.currentTimeMillis()
89             val lastUsedGlobally = usageStatsManager.getLastTimeAnyComponentUsed(pkgName)
90             if (now - lastUsedGlobally < unusedThreshold) {
91                 continue
92             }
93 
94             hibernationManager.setHibernatingGlobally(pkgName, true)
95             globallyHibernatedApps.add(pkgName)
96         }
97         if (hibernatedApps.isNotEmpty()) {
98             HibernatedPackagesLiveData.update()
99         }
100         if (DEBUG_HIBERNATION) {
101             DumpableLog.i(
102                 LOG_TAG,
103                 "Done hibernating apps $hibernatedApps \n " +
104                     "Globally hibernating apps $globallyHibernatedApps"
105             )
106         }
107 
108         return hibernatedApps
109     }
110 }
111