• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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
18 
19 import android.Manifest
20 import android.Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION
21 import android.accessibilityservice.AccessibilityService
22 import android.annotation.SuppressLint
23 import android.app.ActivityManager
24 import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE
25 import android.app.AppOpsManager
26 import android.app.Notification
27 import android.app.NotificationChannel
28 import android.app.NotificationManager
29 import android.app.PendingIntent
30 import android.app.PendingIntent.FLAG_IMMUTABLE
31 import android.app.PendingIntent.FLAG_ONE_SHOT
32 import android.app.PendingIntent.FLAG_UPDATE_CURRENT
33 import android.app.admin.DeviceAdminReceiver
34 import android.app.admin.DevicePolicyManager
35 import android.app.job.JobInfo
36 import android.app.job.JobParameters
37 import android.app.job.JobScheduler
38 import android.app.job.JobService
39 import android.app.role.RoleManager
40 import android.app.usage.UsageStats
41 import android.app.usage.UsageStatsManager.INTERVAL_DAILY
42 import android.app.usage.UsageStatsManager.INTERVAL_MONTHLY
43 import android.content.BroadcastReceiver
44 import android.content.ComponentName
45 import android.content.Context
46 import android.content.Intent
47 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
48 import android.content.Intent.FLAG_RECEIVER_FOREGROUND
49 import android.content.SharedPreferences
50 import android.content.pm.PackageManager
51 import android.content.pm.PackageManager.PERMISSION_GRANTED
52 import android.os.Build
53 import android.os.Bundle
54 import android.os.Process
55 import android.os.SystemClock
56 import android.os.UserHandle
57 import android.os.UserManager
58 import android.printservice.PrintService
59 import android.provider.DeviceConfig
60 import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
61 import android.provider.Settings
62 import android.safetycenter.SafetyCenterManager
63 import android.safetycenter.SafetyEvent
64 import android.safetycenter.SafetySourceData
65 import android.safetycenter.SafetySourceIssue
66 import android.safetycenter.SafetySourceIssue.Action
67 import android.service.autofill.AutofillService
68 import android.service.dreams.DreamService
69 import android.service.notification.NotificationListenerService
70 import android.service.voice.VoiceInteractionService
71 import android.service.wallpaper.WallpaperService
72 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
73 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS
74 import android.util.Log
75 import android.view.inputmethod.InputMethod
76 import androidx.annotation.ChecksSdkIntAtLeast
77 import androidx.annotation.MainThread
78 import androidx.annotation.RequiresApi
79 import androidx.lifecycle.MutableLiveData
80 import androidx.preference.PreferenceManager
81 import com.android.modules.utils.build.SdkLevel
82 import com.android.permissioncontroller.Constants
83 import com.android.permissioncontroller.DumpableLog
84 import com.android.permissioncontroller.PermissionControllerApplication
85 import com.android.permissioncontroller.R
86 import com.android.permissioncontroller.hibernation.v31.HibernationController
87 import com.android.permissioncontroller.hibernation.v31.InstallerPackagesLiveData
88 import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
89 import com.android.permissioncontroller.permission.data.AppOpLiveData
90 import com.android.permissioncontroller.permission.data.BroadcastReceiverLiveData
91 import com.android.permissioncontroller.permission.data.CarrierPrivilegedStatusLiveData
92 import com.android.permissioncontroller.permission.data.DataRepositoryForPackage
93 import com.android.permissioncontroller.permission.data.HasIntentAction
94 import com.android.permissioncontroller.permission.data.LauncherPackagesLiveData
95 import com.android.permissioncontroller.permission.data.ServiceLiveData
96 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
97 import com.android.permissioncontroller.permission.data.UsageStatsLiveData
98 import com.android.permissioncontroller.permission.data.get
99 import com.android.permissioncontroller.permission.data.getUnusedPackages
100 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
101 import com.android.permissioncontroller.permission.service.revokeAppPermissions
102 import com.android.permissioncontroller.permission.utils.KotlinUtils
103 import com.android.permissioncontroller.permission.utils.StringUtils
104 import com.android.permissioncontroller.permission.utils.Utils
105 import com.android.permissioncontroller.permission.utils.forEachInParallel
106 import java.util.Date
107 import java.util.Random
108 import java.util.concurrent.TimeUnit
109 import kotlinx.coroutines.Dispatchers.Main
110 import kotlinx.coroutines.GlobalScope
111 import kotlinx.coroutines.Job
112 import kotlinx.coroutines.launch
113 
114 private const val LOG_TAG = "HibernationPolicy"
115 const val DEBUG_OVERRIDE_THRESHOLDS = false
116 // TODO eugenesusla: temporarily enabled for extra logs during dogfooding
117 const val DEBUG_HIBERNATION_POLICY = true || DEBUG_OVERRIDE_THRESHOLDS
118 
119 private var SKIP_NEXT_RUN = false
120 
121 private val DEFAULT_UNUSED_THRESHOLD_MS = TimeUnit.DAYS.toMillis(90)
122 
123 fun getUnusedThresholdMs() = when {
124     DEBUG_OVERRIDE_THRESHOLDS -> TimeUnit.SECONDS.toMillis(1)
125     else -> DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
126             Utils.PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS,
127             DEFAULT_UNUSED_THRESHOLD_MS)
128 }
129 
130 private val DEFAULT_CHECK_FREQUENCY_MS = TimeUnit.DAYS.toMillis(15)
131 
getCheckFrequencyMsnull132 private fun getCheckFrequencyMs() = DeviceConfig.getLong(
133     DeviceConfig.NAMESPACE_PERMISSIONS,
134         Utils.PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS,
135         DEFAULT_CHECK_FREQUENCY_MS)
136 
137 // Intentionally kept value of the key same as before because we want to continue reading value of
138 // this shared preference stored by previous versions of PermissionController
139 const val PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING = "first_boot_time"
140 const val PREF_KEY_BOOT_TIME_SNAPSHOT = "ah_boot_time_snapshot"
141 const val PREF_KEY_ELAPSED_REALTIME_SNAPSHOT = "ah_elapsed_realtime_snapshot"
142 
143 private const val PREFS_FILE_NAME = "unused_apps_prefs"
144 private const val PREF_KEY_UNUSED_APPS_REVIEW = "unused_apps_need_review"
145 const val SNAPSHOT_UNINITIALIZED = -1L
146 private const val ACTION_SET_UP_HIBERNATION =
147     "com.android.permissioncontroller.action.SET_UP_HIBERNATION"
148 val ONE_DAY_MS = TimeUnit.DAYS.toMillis(1)
149 
150 fun isHibernationEnabled(): Boolean {
151     return SdkLevel.isAtLeastS() &&
152         DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, Utils.PROPERTY_APP_HIBERNATION_ENABLED,
153             true /* defaultValue */)
154 }
155 
156 /**
157  * Whether hibernation defaults on and affects apps that target pre-S. Has no effect if
158  * [isHibernationEnabled] is false.
159  */
hibernationTargetsPreSAppsnull160 fun hibernationTargetsPreSApps(): Boolean {
161     return DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
162         Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS,
163         false /* defaultValue */)
164 }
165 
166 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
isSystemExemptFromHibernationEnablednull167 fun isSystemExemptFromHibernationEnabled(): Boolean {
168     return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
169             Utils.PROPERTY_SYSTEM_EXEMPT_HIBERNATION_ENABLED,
170             true /* defaultValue */)
171 }
172 
173 /**
174  * Remove the unused apps notification.
175  */
cancelUnusedAppsNotificationnull176 fun cancelUnusedAppsNotification(context: Context) {
177     context.getSystemService(NotificationManager::class.java)!!.cancel(
178         HibernationJobService::class.java.simpleName,
179         Constants.UNUSED_APPS_NOTIFICATION_ID)
180 }
181 
182 /**
183  * Checks if we need to show the safety center card and sends the appropriate source data. If
184  * the user has not reviewed the latest auto-revoked apps, we show the card. Otherwise, we ensure
185  * nothing is shown.
186  */
187 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
rescanAndPushDataToSafetyCenternull188 fun rescanAndPushDataToSafetyCenter(
189     context: Context,
190     sessionId: Long,
191     safetyEvent: SafetyEvent,
192 ) {
193     val safetyCenterManager: SafetyCenterManager =
194         context.getSystemService(SafetyCenterManager::class.java)!!
195     if (getUnusedAppsReviewNeeded(context)) {
196         val seeUnusedAppsAction = Action.Builder(
197             Constants.UNUSED_APPS_SAFETY_CENTER_SEE_UNUSED_APPS_ID,
198             context.getString(R.string.unused_apps_safety_center_action_title),
199             makeUnusedAppsIntent(context, sessionId))
200             .build()
201 
202         val issue = SafetySourceIssue.Builder(
203             Constants.UNUSED_APPS_SAFETY_CENTER_ISSUE_ID,
204             context.getString(R.string.unused_apps_safety_center_card_title),
205             context.getString(R.string.unused_apps_safety_center_card_content),
206             SafetySourceData.SEVERITY_LEVEL_INFORMATION,
207             Constants.UNUSED_APPS_SAFETY_CENTER_ISSUE_ID)
208             .addAction(seeUnusedAppsAction)
209             .setOnDismissPendingIntent(makeDismissIntent(context, sessionId))
210             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
211             .build()
212 
213         val safetySourceData = SafetySourceData.Builder()
214             .addIssue(issue)
215             .build()
216 
217         safetyCenterManager.setSafetySourceData(
218             Constants.UNUSED_APPS_SAFETY_CENTER_SOURCE_ID,
219             safetySourceData,
220             safetyEvent)
221     } else {
222         safetyCenterManager.setSafetySourceData(
223             Constants.UNUSED_APPS_SAFETY_CENTER_SOURCE_ID,
224             /* safetySourceData= */ null,
225             safetyEvent)
226     }
227 }
228 
229 /**
230  * Set whether we show the safety center card to the user to review their auto-revoked permissions.
231  */
setUnusedAppsReviewNeedednull232 fun setUnusedAppsReviewNeeded(context: Context, needsReview: Boolean) {
233     val sharedPreferences = context.sharedPreferences
234     if (sharedPreferences.contains(PREF_KEY_UNUSED_APPS_REVIEW) &&
235         sharedPreferences.getBoolean(PREF_KEY_UNUSED_APPS_REVIEW, false) == needsReview) {
236         return
237     }
238     sharedPreferences.edit().putBoolean(PREF_KEY_UNUSED_APPS_REVIEW, needsReview).apply()
239 }
240 
getUnusedAppsReviewNeedednull241 private fun getUnusedAppsReviewNeeded(context: Context): Boolean {
242     return context.sharedPreferences.getBoolean(PREF_KEY_UNUSED_APPS_REVIEW, false)
243 }
244 
245 /**
246  * Receiver of the following broadcasts:
247  * <ul>
248  *   <li> {@link Intent.ACTION_BOOT_COMPLETED}
249  *   <li> {@link #ACTION_SET_UP_HIBERNATION}
250  *   <li> {@link Intent.ACTION_TIME_CHANGED}
251  *   <li> {@link Intent.ACTION_TIMEZONE_CHANGED}
252  * </ul>
253  */
254 class HibernationBroadcastReceiver : BroadcastReceiver() {
255 
onReceivenull256     override fun onReceive(context: Context, intent: Intent) {
257         val action = intent.action
258         if (action == Intent.ACTION_BOOT_COMPLETED || action == ACTION_SET_UP_HIBERNATION) {
259             if (DEBUG_HIBERNATION_POLICY) {
260                 DumpableLog.i(LOG_TAG, "scheduleHibernationJob " +
261                   "with frequency ${getCheckFrequencyMs()}ms " +
262                   "and threshold ${getUnusedThresholdMs()}ms")
263             }
264 
265             initStartTimeOfUnusedAppTracking(context.sharedPreferences)
266 
267             // If this user is a profile, then its hibernation/auto-revoke will be handled by the
268             // primary user
269             if (isProfile(context)) {
270                 if (DEBUG_HIBERNATION_POLICY) {
271                     DumpableLog.i(LOG_TAG,
272                                   "user ${Process.myUserHandle().identifier} is a profile." +
273                                     " Not running hibernation job.")
274                 }
275                 return
276             } else if (DEBUG_HIBERNATION_POLICY) {
277                 DumpableLog.i(LOG_TAG,
278                               "user ${Process.myUserHandle().identifier} is a profile" +
279                                 "owner. Running hibernation job.")
280             }
281 
282             if (isNewJobScheduleRequired(context)) {
283                 // periodic jobs normally run immediately, which is unnecessarily premature
284                 SKIP_NEXT_RUN = true
285                 val jobInfo = JobInfo.Builder(
286                     Constants.HIBERNATION_JOB_ID,
287                     ComponentName(context, HibernationJobService::class.java))
288                     .setPeriodic(getCheckFrequencyMs())
289                     // persist this job across boots
290                     .setPersisted(true)
291                     .build()
292                 val status =
293                     context.getSystemService(JobScheduler::class.java)!!.schedule(jobInfo)
294                 if (status != JobScheduler.RESULT_SUCCESS) {
295                     DumpableLog.e(LOG_TAG, "Could not schedule " +
296                       "${HibernationJobService::class.java.simpleName}: $status")
297                 }
298             }
299         }
300         if (action == Intent.ACTION_TIME_CHANGED || action == Intent.ACTION_TIMEZONE_CHANGED) {
301             adjustStartTimeOfUnusedAppTracking(context.sharedPreferences)
302         }
303     }
304 
305     // UserManager#isProfile was already a systemAPI, linter started complaining after it
306     // was exposed as a public API thinking it was a newly exposed API.
307     @SuppressLint("NewApi")
isProfilenull308     private fun isProfile(context: Context): Boolean {
309         val userManager = context.getSystemService(UserManager::class.java)!!
310         return userManager.isProfile
311     }
312 
313     /**
314      * Returns whether a new job needs to be scheduled. A persisted job is used to keep the schedule
315      * across boots, but that job needs to be scheduled a first time and whenever the check
316      * frequency changes.
317      */
isNewJobScheduleRequirednull318     private fun isNewJobScheduleRequired(context: Context): Boolean {
319         // check if the job is already scheduled or needs a change
320         var scheduleNewJob = false
321         val existingJob: JobInfo? = context.getSystemService(JobScheduler::class.java)!!
322             .getPendingJob(Constants.HIBERNATION_JOB_ID)
323         if (existingJob == null) {
324             if (DEBUG_HIBERNATION_POLICY) {
325                 DumpableLog.i(LOG_TAG, "No existing job, scheduling a new one")
326             }
327             scheduleNewJob = true
328         } else if (existingJob.intervalMillis != getCheckFrequencyMs()) {
329             if (DEBUG_HIBERNATION_POLICY) {
330                 DumpableLog.i(LOG_TAG, "Interval frequency has changed, updating job")
331             }
332             scheduleNewJob = true
333         } else {
334             if (DEBUG_HIBERNATION_POLICY) {
335                 DumpableLog.i(LOG_TAG, "Job already scheduled.")
336             }
337         }
338         return scheduleNewJob
339     }
340 }
341 
342 /**
343  * Gets apps that are unused and should hibernate as a map of the user and their hibernateable apps.
344  */
345 @MainThread
getAppsToHibernatenull346 private suspend fun getAppsToHibernate(
347     context: Context,
348 ): Map<UserHandle, List<LightPackageInfo>> {
349     val now = System.currentTimeMillis()
350     val startTimeOfUnusedAppTracking = getStartTimeOfUnusedAppTracking(context.sharedPreferences)
351 
352     val allPackagesByUser = AllPackageInfosLiveData.getInitializedValue(forceUpdate = true)
353     val allPackagesByUserByUid = allPackagesByUser.mapValues { (_, pkgs) ->
354         pkgs.groupBy { pkg -> pkg.uid }
355     }
356     val unusedApps = allPackagesByUser.toMutableMap()
357 
358     val userStats = UsageStatsLiveData[getUnusedThresholdMs(),
359         if (DEBUG_OVERRIDE_THRESHOLDS) INTERVAL_DAILY else INTERVAL_MONTHLY].getInitializedValue()
360     if (DEBUG_HIBERNATION_POLICY) {
361         for ((user, stats) in userStats) {
362             DumpableLog.i(LOG_TAG, "Usage stats for user ${user.identifier}: " +
363                     stats.map { stat ->
364                         stat.packageName to Date(stat.lastTimePackageUsed())
365                     }.toMap())
366         }
367     }
368     for (user in unusedApps.keys.toList()) {
369         if (user !in userStats.keys) {
370             if (DEBUG_HIBERNATION_POLICY) {
371                 DumpableLog.i(LOG_TAG, "Ignoring user ${user.identifier}")
372             }
373             unusedApps.remove(user)
374         }
375     }
376 
377     for ((user, stats) in userStats) {
378         var unusedUserApps = unusedApps[user] ?: continue
379 
380         unusedUserApps = unusedUserApps.filter { packageInfo ->
381             val pkgName = packageInfo.packageName
382 
383             val uidPackages = allPackagesByUserByUid[user]!![packageInfo.uid]
384                     ?.map { info -> info.packageName } ?: emptyList()
385             if (pkgName !in uidPackages) {
386                 Log.wtf(LOG_TAG, "Package $pkgName not among packages for " +
387                         "its uid ${packageInfo.uid}: $uidPackages")
388             }
389             var lastTimePkgUsed: Long = stats.lastTimePackageUsed(uidPackages)
390 
391             // Limit by install time
392             lastTimePkgUsed = Math.max(lastTimePkgUsed, packageInfo.firstInstallTime)
393 
394             // Limit by first boot time
395             lastTimePkgUsed = Math.max(lastTimePkgUsed, startTimeOfUnusedAppTracking)
396 
397             // Handle cross-profile apps
398             if (context.isPackageCrossProfile(pkgName)) {
399                 for ((otherUser, otherStats) in userStats) {
400                     if (otherUser == user) {
401                         continue
402                     }
403                     lastTimePkgUsed =
404                         maxOf(lastTimePkgUsed, otherStats.lastTimePackageUsed(pkgName))
405                 }
406             }
407 
408             // Threshold check - whether app is unused
409             now - lastTimePkgUsed > getUnusedThresholdMs()
410         }
411 
412         unusedApps[user] = unusedUserApps
413         if (DEBUG_HIBERNATION_POLICY) {
414             DumpableLog.i(LOG_TAG, "Unused apps for user ${user.identifier}: " +
415                     "${unusedUserApps.map { it.packageName }}")
416         }
417     }
418 
419     val appsToHibernate = mutableMapOf<UserHandle, List<LightPackageInfo>>()
420     val userManager = context.getSystemService(UserManager::class.java)
421     for ((user, userApps) in unusedApps) {
422         if (userManager == null || !userManager.isUserUnlocked(user)) {
423             DumpableLog.w(LOG_TAG, "Skipping $user - locked direct boot state")
424             continue
425         }
426         var userAppsToHibernate = mutableListOf<LightPackageInfo>()
427         userApps.forEachInParallel(Main) { pkg: LightPackageInfo ->
428             if (isPackageHibernationExemptBySystem(pkg, user)) {
429                 return@forEachInParallel
430             }
431 
432             if (isPackageHibernationExemptByUser(context, pkg)) {
433                 return@forEachInParallel
434             }
435 
436             val packageName = pkg.packageName
437             val packageImportance = context
438                 .getSystemService(ActivityManager::class.java)!!
439                 .getPackageImportance(packageName)
440             if (packageImportance <= IMPORTANCE_CANT_SAVE_STATE) {
441                 // Process is running in a state where it should not be killed
442                 DumpableLog.i(LOG_TAG,
443                     "Skipping hibernation - $packageName running with importance " +
444                         "$packageImportance")
445                 return@forEachInParallel
446             }
447 
448             if (DEBUG_HIBERNATION_POLICY) {
449                 DumpableLog.i(LOG_TAG, "unused app $packageName - last used on " +
450                     userStats[user]?.lastTimePackageUsed(packageName)?.let(::Date))
451             }
452 
453             synchronized(userAppsToHibernate) {
454                 userAppsToHibernate.add(pkg)
455             }
456         }
457         appsToHibernate.put(user, userAppsToHibernate)
458     }
459     return appsToHibernate
460 }
461 
462 /**
463  * Gets the last time we consider the package used based off its usage stats. On pre-S devices
464  * this looks at last time visible which tracks explicit usage. In S, we add component usage
465  * which tracks various forms of implicit usage (e.g. service bindings).
466  */
UsageStatsnull467 fun UsageStats.lastTimePackageUsed(): Long {
468     var lastTimePkgUsed = this.lastTimeVisible
469     if (SdkLevel.isAtLeastS()) {
470         lastTimePkgUsed = maxOf(lastTimePkgUsed, this.lastTimeAnyComponentUsed)
471     }
472     return lastTimePkgUsed
473 }
474 
Listnull475 private fun List<UsageStats>.lastTimePackageUsed(pkgNames: List<String>): Long {
476     var result = 0L
477     for (stat in this) {
478         if (stat.packageName in pkgNames) {
479             result = Math.max(result, stat.lastTimePackageUsed())
480         }
481     }
482     return result
483 }
484 
Listnull485 private fun List<UsageStats>.lastTimePackageUsed(pkgName: String): Long {
486     return lastTimePackageUsed(listOf(pkgName))
487 }
488 
489 /**
490  * Checks if the given package is exempt from hibernation in a way that's not user-overridable
491  */
isPackageHibernationExemptBySystemnull492 suspend fun isPackageHibernationExemptBySystem(
493     pkg: LightPackageInfo,
494     user: UserHandle,
495 ): Boolean {
496     if (!LauncherPackagesLiveData.getInitializedValue().contains(pkg.packageName)) {
497         if (DEBUG_HIBERNATION_POLICY) {
498             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - Package is not on launcher")
499         }
500         return true
501     }
502     if (!ExemptServicesLiveData[user]
503             .getInitializedValue()[pkg.packageName]
504             .isNullOrEmpty()) {
505         return true
506     }
507     if (Utils.isUserDisabledOrWorkProfile(user)) {
508         if (DEBUG_HIBERNATION_POLICY) {
509             DumpableLog.i(LOG_TAG,
510                     "Exempted ${pkg.packageName} - $user is disabled or a work profile")
511         }
512         return true
513     }
514 
515     if (pkg.uid == Process.SYSTEM_UID){
516         if (DEBUG_HIBERNATION_POLICY) {
517             DumpableLog.i(LOG_TAG,
518                 "Exempted ${pkg.packageName} - Package shares system uid")
519         }
520         return true
521     }
522 
523     val context = PermissionControllerApplication.get()
524     if (context.getSystemService(DevicePolicyManager::class.java)!!.isDeviceManaged) {
525         // TODO(b/237065504): Use proper system API to check if the device is financed in U.
526         val isFinancedDevice = Settings.Global.getInt(
527                 context.contentResolver, "device_owner_type", 0) == 1
528         if (!isFinancedDevice) {
529             if (DEBUG_HIBERNATION_POLICY) {
530                 DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - device is managed")
531             }
532             return true
533         }
534     }
535 
536     val carrierPrivilegedStatus = CarrierPrivilegedStatusLiveData[pkg.packageName]
537             .getInitializedValue()
538     if (carrierPrivilegedStatus != CARRIER_PRIVILEGE_STATUS_HAS_ACCESS &&
539             carrierPrivilegedStatus != CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
540         DumpableLog.w(LOG_TAG, "Error carrier privileged status for ${pkg.packageName}: " +
541                 carrierPrivilegedStatus)
542     }
543     if (carrierPrivilegedStatus == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
544         if (DEBUG_HIBERNATION_POLICY) {
545             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - carrier privileged")
546         }
547         return true
548     }
549 
550     if (PermissionControllerApplication.get()
551             .packageManager
552             .checkPermission(
553                     Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
554                     pkg.packageName) == PERMISSION_GRANTED) {
555         if (DEBUG_HIBERNATION_POLICY) {
556             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} " +
557                     "- holder of READ_PRIVILEGED_PHONE_STATE")
558         }
559         return true
560     }
561 
562     val emergencyRoleHolders = context.getSystemService(android.app.role.RoleManager::class.java)!!
563             .getRoleHolders(RoleManager.ROLE_EMERGENCY)
564     if (emergencyRoleHolders.contains(pkg.packageName)) {
565         if (DEBUG_HIBERNATION_POLICY) {
566             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - emergency app")
567         }
568         return true
569     }
570 
571     if (SdkLevel.isAtLeastS()) {
572         val hasInstallOrUpdatePermissions =
573                 context.checkPermission(
574                         Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, pkg.uid) ==
575                                 PERMISSION_GRANTED ||
576                 context.checkPermission(
577                         Manifest.permission.INSTALL_PACKAGE_UPDATES, -1 /* pid */, pkg.uid) ==
578                                 PERMISSION_GRANTED
579         val hasUpdatePackagesWithoutUserActionPermission =
580                 context.checkPermission(
581                         UPDATE_PACKAGES_WITHOUT_USER_ACTION, -1 /* pid */, pkg.uid) ==
582                                 PERMISSION_GRANTED
583         val isInstallerOfRecord =
584                 InstallerPackagesLiveData[user].getInitializedValue().contains(pkg.packageName) &&
585                         hasUpdatePackagesWithoutUserActionPermission
586         // Grant if app w/ privileged install/update permissions or app is an installer app that
587         // updates packages without user action.
588         if (hasInstallOrUpdatePermissions || isInstallerOfRecord) {
589             if (DEBUG_HIBERNATION_POLICY) {
590                 DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - installer app")
591             }
592             return true
593         }
594 
595         val roleHolders = context.getSystemService(android.app.role.RoleManager::class.java)!!
596                 .getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)
597         if (roleHolders.contains(pkg.packageName)) {
598             if (DEBUG_HIBERNATION_POLICY) {
599                 DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - wellbeing app")
600             }
601             return true
602         }
603     }
604 
605     if (SdkLevel.isAtLeastT()) {
606         val roleHolders = context.getSystemService(android.app.role.RoleManager::class.java)!!
607             .getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT)
608         if (roleHolders.contains(pkg.packageName)) {
609             if (DEBUG_HIBERNATION_POLICY) {
610                 DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - device policy manager app")
611             }
612             return true
613         }
614     }
615 
616     if (isSystemExemptFromHibernationEnabled() && AppOpLiveData[pkg.packageName,
617           AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
618           pkg.uid].getInitializedValue() == AppOpsManager.MODE_ALLOWED) {
619         if (DEBUG_HIBERNATION_POLICY) {
620             DumpableLog.i(
621                 LOG_TAG,
622                 "Exempted ${pkg.packageName} - has OP_SYSTEM_EXEMPT_FROM_HIBERNATION"
623             )
624         }
625         return true
626     }
627 
628     return false
629 }
630 
631 /**
632  * Checks if the given package is exempt from hibernation/auto revoke in a way that's
633  * user-overridable
634  */
isPackageHibernationExemptByUsernull635 suspend fun isPackageHibernationExemptByUser(
636     context: Context,
637     pkg: LightPackageInfo,
638 ): Boolean {
639     val packageName = pkg.packageName
640     val packageUid = pkg.uid
641 
642     val allowlistAppOpMode =
643         AppOpLiveData[packageName,
644             AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid]
645             .getInitializedValue()
646     if (allowlistAppOpMode == AppOpsManager.MODE_DEFAULT) {
647         // Initial state - allowlist not explicitly overridden by either user or installer
648         if (DEBUG_OVERRIDE_THRESHOLDS) {
649             // Suppress exemptions to allow debugging
650             return false
651         }
652 
653         if (hibernationTargetsPreSApps()) {
654             // Default on if overridden
655             return false
656         }
657 
658         // Q- packages exempt by default, except R- on Auto since Auto-Revoke was skipped in R
659         val maxTargetSdkVersionForExemptApps =
660                 if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
661                     android.os.Build.VERSION_CODES.R
662                 } else {
663                     android.os.Build.VERSION_CODES.Q
664                 }
665 
666         return pkg.targetSdkVersion <= maxTargetSdkVersionForExemptApps
667     }
668     // Check whether user/installer exempt
669     return allowlistAppOpMode != AppOpsManager.MODE_ALLOWED
670 }
671 
Contextnull672 private fun Context.isPackageCrossProfile(pkg: String): Boolean {
673     return packageManager.checkPermission(
674         Manifest.permission.INTERACT_ACROSS_PROFILES, pkg) == PERMISSION_GRANTED ||
675         packageManager.checkPermission(
676             Manifest.permission.INTERACT_ACROSS_USERS, pkg) == PERMISSION_GRANTED ||
677         packageManager.checkPermission(
678             Manifest.permission.INTERACT_ACROSS_USERS_FULL, pkg) == PERMISSION_GRANTED
679 }
680 
681 val Context.sharedPreferences: SharedPreferences
682     get() {
683     return PreferenceManager.getDefaultSharedPreferences(this)
684 }
685 
686 internal class SystemTime {
687     var actualSystemTime: Long = SNAPSHOT_UNINITIALIZED
688     var actualRealtime: Long = SNAPSHOT_UNINITIALIZED
689     var diffSystemTime: Long = SNAPSHOT_UNINITIALIZED
690 }
691 
getSystemTimenull692 private fun getSystemTime(sharedPreferences: SharedPreferences): SystemTime {
693     val systemTime = SystemTime()
694     val systemTimeSnapshot = sharedPreferences.getLong(PREF_KEY_BOOT_TIME_SNAPSHOT,
695                                                        SNAPSHOT_UNINITIALIZED)
696     if (systemTimeSnapshot == SNAPSHOT_UNINITIALIZED) {
697         DumpableLog.e(LOG_TAG, "PREF_KEY_BOOT_TIME_SNAPSHOT is not initialized")
698         return systemTime
699     }
700 
701     val realtimeSnapshot = sharedPreferences.getLong(PREF_KEY_ELAPSED_REALTIME_SNAPSHOT,
702                                                      SNAPSHOT_UNINITIALIZED)
703     if (realtimeSnapshot == SNAPSHOT_UNINITIALIZED) {
704         DumpableLog.e(LOG_TAG, "PREF_KEY_ELAPSED_REALTIME_SNAPSHOT is not initialized")
705         return systemTime
706     }
707     systemTime.actualSystemTime = System.currentTimeMillis()
708     systemTime.actualRealtime = SystemClock.elapsedRealtime()
709     val expectedSystemTime = systemTime.actualRealtime - realtimeSnapshot + systemTimeSnapshot
710     systemTime.diffSystemTime = systemTime.actualSystemTime - expectedSystemTime
711     return systemTime
712 }
713 
getStartTimeOfUnusedAppTrackingnull714 fun getStartTimeOfUnusedAppTracking(sharedPreferences: SharedPreferences): Long {
715     val startTimeOfUnusedAppTracking = sharedPreferences.getLong(
716         PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING, SNAPSHOT_UNINITIALIZED)
717 
718     // If the preference is not initialized then use the current system time.
719     if (startTimeOfUnusedAppTracking == SNAPSHOT_UNINITIALIZED) {
720         val actualSystemTime = System.currentTimeMillis()
721         sharedPreferences.edit()
722             .putLong(PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING, actualSystemTime).apply()
723         return actualSystemTime
724     }
725 
726     val diffSystemTime = getSystemTime(sharedPreferences).diffSystemTime
727     // If the value stored is older than a day adjust start time.
728     if (diffSystemTime > ONE_DAY_MS) {
729         adjustStartTimeOfUnusedAppTracking(sharedPreferences)
730     }
731     return sharedPreferences.getLong(PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING,
732                                      SNAPSHOT_UNINITIALIZED)
733 }
734 
initStartTimeOfUnusedAppTrackingnull735 private fun initStartTimeOfUnusedAppTracking(sharedPreferences: SharedPreferences) {
736     val systemTimeSnapshot = System.currentTimeMillis()
737     if (sharedPreferences
738             .getLong(PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING, SNAPSHOT_UNINITIALIZED)
739         == SNAPSHOT_UNINITIALIZED) {
740         sharedPreferences.edit()
741             .putLong(PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING, systemTimeSnapshot).apply()
742     }
743     val realtimeSnapshot = SystemClock.elapsedRealtime()
744     sharedPreferences.edit()
745         .putLong(PREF_KEY_BOOT_TIME_SNAPSHOT, systemTimeSnapshot)
746         .putLong(PREF_KEY_ELAPSED_REALTIME_SNAPSHOT, realtimeSnapshot)
747         .apply()
748 }
749 
adjustStartTimeOfUnusedAppTrackingnull750 private fun adjustStartTimeOfUnusedAppTracking(sharedPreferences: SharedPreferences) {
751     val systemTime = getSystemTime(sharedPreferences)
752     val startTimeOfUnusedAppTracking =
753         sharedPreferences.getLong(PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING,
754                                   SNAPSHOT_UNINITIALIZED)
755     if (startTimeOfUnusedAppTracking == SNAPSHOT_UNINITIALIZED) {
756         DumpableLog.e(LOG_TAG, "PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING is not initialized")
757         return
758     }
759     val adjustedStartTimeOfUnusedAppTracking =
760         startTimeOfUnusedAppTracking + systemTime.diffSystemTime
761     sharedPreferences.edit()
762         .putLong(PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING, adjustedStartTimeOfUnusedAppTracking)
763         .putLong(PREF_KEY_BOOT_TIME_SNAPSHOT, systemTime.actualSystemTime)
764         .putLong(PREF_KEY_ELAPSED_REALTIME_SNAPSHOT, systemTime.actualRealtime)
765         .apply()
766 }
767 
768 /**
769  * Make intent to go to unused apps page.
770  */
makeUnusedAppsIntentnull771 private fun makeUnusedAppsIntent(context: Context, sessionId: Long): PendingIntent {
772     val clickIntent = Intent(Intent.ACTION_MANAGE_UNUSED_APPS).apply {
773         putExtra(Constants.EXTRA_SESSION_ID, sessionId)
774         flags = FLAG_ACTIVITY_NEW_TASK
775     }
776     val pendingIntent = PendingIntent.getActivity(context, 0, clickIntent,
777         FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
778     return pendingIntent
779 }
780 
781 /**
782  * Make intent for when safety center card is dismissed.
783  */
makeDismissIntentnull784 private fun makeDismissIntent(context: Context, sessionId: Long): PendingIntent {
785     val dismissIntent = Intent(context, DismissHandler::class.java).apply {
786         putExtra(Constants.EXTRA_SESSION_ID, sessionId)
787         flags = FLAG_RECEIVER_FOREGROUND
788     }
789     return PendingIntent.getBroadcast(context, /* requestCode= */ 0, dismissIntent,
790         FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
791 }
792 
793 /**
794  * Broadcast receiver class for when safety center card is dismissed.
795  */
796 class DismissHandler : BroadcastReceiver() {
onReceivenull797     override fun onReceive(context: Context?, intent: Intent?) {
798         setUnusedAppsReviewNeeded(context!!, false)
799     }
800 }
801 
802 /**
803  * A job to check for apps unused in the last [getUnusedThresholdMs]ms every
804  * [getCheckFrequencyMs]ms and hibernate the app / revoke their runtime permissions.
805  */
806 class HibernationJobService : JobService() {
807     var job: Job? = null
808     var jobStartTime: Long = -1L
809 
onStartJobnull810     override fun onStartJob(params: JobParameters?): Boolean {
811         if (DEBUG_HIBERNATION_POLICY) {
812             DumpableLog.i(LOG_TAG, "onStartJob")
813         }
814 
815         if (SKIP_NEXT_RUN) {
816             SKIP_NEXT_RUN = false
817             if (DEBUG_HIBERNATION_POLICY) {
818                 DumpableLog.i(LOG_TAG, "Skipping auto revoke first run when scheduled by system")
819             }
820             jobFinished(params, false)
821             return true
822         }
823 
824         jobStartTime = System.currentTimeMillis()
825         job = GlobalScope.launch(Main) {
826             try {
827                 var sessionId = Constants.INVALID_SESSION_ID
828                 while (sessionId == Constants.INVALID_SESSION_ID) {
829                     sessionId = Random().nextLong()
830                 }
831 
832                 val appsToHibernate = getAppsToHibernate(this@HibernationJobService)
833                 var hibernatedApps: Set<Pair<String, UserHandle>> = emptySet()
834                 if (isHibernationEnabled()) {
835                     val hibernationController =
836                         HibernationController(this@HibernationJobService, getUnusedThresholdMs(),
837                             hibernationTargetsPreSApps())
838                     hibernatedApps = hibernationController.hibernateApps(appsToHibernate)
839                 }
840                 val revokedApps = revokeAppPermissions(
841                         appsToHibernate, this@HibernationJobService, sessionId)
842                 val unusedApps: Set<Pair<String, UserHandle>> = hibernatedApps + revokedApps
843                 if (unusedApps.isNotEmpty()) {
844                     showUnusedAppsNotification(unusedApps.size, sessionId)
845                     if (SdkLevel.isAtLeastT() &&
846                         revokedApps.isNotEmpty() &&
847                         getSystemService(SafetyCenterManager::class.java)!!.isSafetyCenterEnabled) {
848                         setUnusedAppsReviewNeeded(this@HibernationJobService, true)
849                         rescanAndPushDataToSafetyCenter(
850                             this@HibernationJobService,
851                             sessionId,
852                             SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED)
853                                 .build())
854                     }
855                 }
856             } catch (e: Exception) {
857                 DumpableLog.e(LOG_TAG, "Failed to auto-revoke permissions", e)
858             }
859             jobFinished(params, false)
860         }
861         return true
862     }
863 
showUnusedAppsNotificationnull864     private suspend fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) {
865         val notificationManager = getSystemService(NotificationManager::class.java)!!
866 
867         val permissionReminderChannel = NotificationChannel(
868                 Constants.PERMISSION_REMINDER_CHANNEL_ID, getString(R.string.permission_reminders),
869                 NotificationManager.IMPORTANCE_LOW)
870         notificationManager.createNotificationChannel(permissionReminderChannel)
871 
872         var notifTitle: String
873         var notifContent: String
874         if (isHibernationEnabled()) {
875             notifTitle = StringUtils.getIcuPluralsString(this,
876                 R.string.unused_apps_notification_title, numUnused)
877             notifContent = getString(R.string.unused_apps_notification_content)
878         } else {
879             notifTitle = getString(R.string.auto_revoke_permission_notification_title)
880             notifContent = getString(R.string.auto_revoke_permission_notification_content)
881         }
882 
883         // Notification won't appear on TV, because notifications are considered distruptive on TV
884         val b = Notification.Builder(this, Constants.PERMISSION_REMINDER_CHANNEL_ID)
885             .setContentTitle(notifTitle)
886             .setContentText(notifContent)
887             .setStyle(Notification.BigTextStyle().bigText(notifContent))
888             .setColor(getColor(android.R.color.system_notification_accent_color))
889             .setAutoCancel(true)
890             .setContentIntent(makeUnusedAppsIntent(this, sessionId))
891         val extras = Bundle()
892         if (SdkLevel.isAtLeastT() &&
893             getSystemService(SafetyCenterManager::class.java)!!.isSafetyCenterEnabled) {
894             val notificationResources = KotlinUtils.getSafetyCenterNotificationResources(this)
895 
896             extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, notificationResources.appLabel)
897             b.setSmallIcon(notificationResources.smallIcon)
898                 .setColor(notificationResources.color)
899                 .addExtras(extras)
900         } else {
901             // Use standard Settings branding
902             Utils.getSettingsLabelForNotifications(applicationContext.packageManager)?.let {
903                 settingsLabel ->
904                 extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, settingsLabel.toString())
905                 b.setSmallIcon(R.drawable.ic_settings_24dp)
906                     .addExtras(extras)
907             }
908         }
909 
910         notificationManager.notify(HibernationJobService::class.java.simpleName,
911                 Constants.UNUSED_APPS_NOTIFICATION_ID, b.build())
912         // Preload the unused packages
913         getUnusedPackages().getInitializedValue()
914     }
915 
onStopJobnull916     override fun onStopJob(params: JobParameters?): Boolean {
917         DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
918         job?.cancel()
919         return true
920     }
921 }
922 
923 /**
924  * Packages using exempt services for the current user (package-name -> list<service-interfaces>
925  * implemented by the package)
926  */
927 class ExemptServicesLiveData(private val user: UserHandle) :
928     SmartUpdateMediatorLiveData<Map<String, List<String>>>() {
929     private val serviceLiveDatas: List<SmartUpdateMediatorLiveData<Set<String>>> = listOf(
930             ServiceLiveData[InputMethod.SERVICE_INTERFACE,
931                     Manifest.permission.BIND_INPUT_METHOD,
932                     user],
933             ServiceLiveData[
934                     NotificationListenerService.SERVICE_INTERFACE,
935                     Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE,
936                     user],
937             ServiceLiveData[
938                     AccessibilityService.SERVICE_INTERFACE,
939                     Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
940                     user],
941             ServiceLiveData[
942                     WallpaperService.SERVICE_INTERFACE,
943                     Manifest.permission.BIND_WALLPAPER,
944                     user],
945             ServiceLiveData[
946                     VoiceInteractionService.SERVICE_INTERFACE,
947                     Manifest.permission.BIND_VOICE_INTERACTION,
948                     user],
949             ServiceLiveData[
950                     PrintService.SERVICE_INTERFACE,
951                     Manifest.permission.BIND_PRINT_SERVICE,
952                     user],
953             ServiceLiveData[
954                     DreamService.SERVICE_INTERFACE,
955                     Manifest.permission.BIND_DREAM_SERVICE,
956                     user],
957             ServiceLiveData[
958                     AutofillService.SERVICE_INTERFACE,
959                     Manifest.permission.BIND_AUTOFILL_SERVICE,
960                     user],
961             ServiceLiveData[
962                     DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE,
963                     Manifest.permission.BIND_DEVICE_ADMIN,
964                     user],
965             BroadcastReceiverLiveData[
966                     DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
967                     Manifest.permission.BIND_DEVICE_ADMIN,
968                     user]
969     )
970 
971     init {
<lambda>null972         serviceLiveDatas.forEach { addSource(it) { update() } }
973     }
974 
onUpdatenull975     override fun onUpdate() {
976         if (serviceLiveDatas.all { it.isInitialized }) {
977             val pksToServices = mutableMapOf<String, MutableList<String>>()
978 
979             serviceLiveDatas.forEach { serviceLD ->
980                 serviceLD.value!!.forEach { packageName ->
981                     pksToServices.getOrPut(packageName, { mutableListOf() })
982                             .add((serviceLD as? HasIntentAction)?.intentAction ?: "???")
983                 }
984             }
985 
986             value = pksToServices
987         }
988     }
989 
990     /**
991      * Repository for ExemptServiceLiveData
992      *
993      * <p> Key value is user
994      */
995     companion object : DataRepositoryForPackage<UserHandle, ExemptServicesLiveData>() {
newValuenull996         override fun newValue(key: UserHandle): ExemptServicesLiveData {
997             return ExemptServicesLiveData(key)
998         }
999     }
1000 }
1001 
1002 /**
1003  * Live data for whether the hibernation feature is enabled or not.
1004  */
1005 object HibernationEnabledLiveData :
1006     MutableLiveData<Boolean>() {
1007     init {
1008         postValue(SdkLevel.isAtLeastS() &&
1009             DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
1010             Utils.PROPERTY_APP_HIBERNATION_ENABLED, true /* defaultValue */))
1011         DeviceConfig.addOnPropertiesChangedListener(
1012             NAMESPACE_APP_HIBERNATION,
1013             PermissionControllerApplication.get().mainExecutor,
propertiesnull1014             { properties ->
1015                 for (key in properties.keyset) {
1016                     if (key == Utils.PROPERTY_APP_HIBERNATION_ENABLED) {
1017                         value = SdkLevel.isAtLeastS() &&
1018                             properties.getBoolean(key, true /* defaultValue */)
1019                         break
1020                     }
1021                 }
1022             }
1023         )
1024     }
1025 }
1026