• 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.app.ActivityManager
23 import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE
24 import android.app.AppOpsManager
25 import android.app.Notification
26 import android.app.NotificationChannel
27 import android.app.NotificationManager
28 import android.app.PendingIntent
29 import android.app.admin.DeviceAdminReceiver
30 import android.app.admin.DevicePolicyManager
31 import android.app.job.JobInfo
32 import android.app.job.JobParameters
33 import android.app.job.JobScheduler
34 import android.app.job.JobService
35 import android.app.usage.UsageStats
36 import android.app.usage.UsageStatsManager.INTERVAL_DAILY
37 import android.app.usage.UsageStatsManager.INTERVAL_MONTHLY
38 import android.content.BroadcastReceiver
39 import android.content.ComponentName
40 import android.content.Context
41 import android.content.Intent
42 import android.content.SharedPreferences
43 import android.content.pm.PackageManager
44 import android.content.pm.PackageManager.PERMISSION_GRANTED
45 import android.os.Bundle
46 import android.os.Process
47 import android.os.UserHandle
48 import android.os.UserManager
49 import android.printservice.PrintService
50 import android.provider.DeviceConfig
51 import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
52 import android.service.autofill.AutofillService
53 import android.service.dreams.DreamService
54 import android.service.notification.NotificationListenerService
55 import android.service.voice.VoiceInteractionService
56 import android.service.wallpaper.WallpaperService
57 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
58 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS
59 import android.util.Log
60 import android.view.inputmethod.InputMethod
61 import androidx.annotation.MainThread
62 import androidx.lifecycle.MutableLiveData
63 import androidx.preference.PreferenceManager
64 import com.android.modules.utils.build.SdkLevel
65 import com.android.permissioncontroller.Constants
66 import com.android.permissioncontroller.DumpableLog
67 import com.android.permissioncontroller.PermissionControllerApplication
68 import com.android.permissioncontroller.R
69 import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
70 import com.android.permissioncontroller.permission.data.AppOpLiveData
71 import com.android.permissioncontroller.permission.data.BroadcastReceiverLiveData
72 import com.android.permissioncontroller.permission.data.CarrierPrivilegedStatusLiveData
73 import com.android.permissioncontroller.permission.data.DataRepositoryForPackage
74 import com.android.permissioncontroller.permission.data.HasIntentAction
75 import com.android.permissioncontroller.permission.data.LauncherPackagesLiveData
76 import com.android.permissioncontroller.permission.data.ServiceLiveData
77 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
78 import com.android.permissioncontroller.permission.data.UsageStatsLiveData
79 import com.android.permissioncontroller.permission.data.get
80 import com.android.permissioncontroller.permission.data.getUnusedPackages
81 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
82 import com.android.permissioncontroller.permission.service.revokeAppPermissions
83 import com.android.permissioncontroller.permission.utils.Utils
84 import com.android.permissioncontroller.permission.utils.forEachInParallel
85 import kotlinx.coroutines.Dispatchers.Main
86 import kotlinx.coroutines.GlobalScope
87 import kotlinx.coroutines.Job
88 import kotlinx.coroutines.launch
89 import java.util.Date
90 import java.util.Random
91 import java.util.concurrent.TimeUnit
92 
93 private const val LOG_TAG = "HibernationPolicy"
94 const val DEBUG_OVERRIDE_THRESHOLDS = false
95 // TODO eugenesusla: temporarily enabled for extra logs during dogfooding
96 const val DEBUG_HIBERNATION_POLICY = true || DEBUG_OVERRIDE_THRESHOLDS
97 
98 private const val AUTO_REVOKE_ENABLED = true
99 
100 private var SKIP_NEXT_RUN = false
101 
102 private val DEFAULT_UNUSED_THRESHOLD_MS = TimeUnit.DAYS.toMillis(90)
103 
104 fun getUnusedThresholdMs() = when {
105     DEBUG_OVERRIDE_THRESHOLDS -> TimeUnit.SECONDS.toMillis(1)
106     !isHibernationEnabled() && !AUTO_REVOKE_ENABLED -> Long.MAX_VALUE
107     else -> DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
108             Utils.PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS,
109             DEFAULT_UNUSED_THRESHOLD_MS)
110 }
111 
112 private val DEFAULT_CHECK_FREQUENCY_MS = TimeUnit.DAYS.toMillis(15)
113 
getCheckFrequencyMsnull114 private fun getCheckFrequencyMs() = DeviceConfig.getLong(
115     DeviceConfig.NAMESPACE_PERMISSIONS,
116         Utils.PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS,
117         DEFAULT_CHECK_FREQUENCY_MS)
118 
119 private val PREF_KEY_FIRST_BOOT_TIME = "first_boot_time"
120 
121 fun isHibernationEnabled(): Boolean {
122     return SdkLevel.isAtLeastS() &&
123         DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, Utils.PROPERTY_APP_HIBERNATION_ENABLED,
124             false /* defaultValue */)
125 }
126 
127 /**
128  * Whether hibernation defaults on and affects apps that target pre-S. Has no effect if
129  * [isHibernationEnabled] is false.
130  */
hibernationTargetsPreSAppsnull131 fun hibernationTargetsPreSApps(): Boolean {
132     return DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
133         Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS,
134         false /* defaultValue */)
135 }
136 
isHibernationJobEnablednull137 fun isHibernationJobEnabled(): Boolean {
138     return getCheckFrequencyMs() > 0 &&
139             getUnusedThresholdMs() > 0 &&
140             getUnusedThresholdMs() != Long.MAX_VALUE
141 }
142 
143 /**
144  * Receiver of the onBoot event.
145  */
146 class HibernationOnBootReceiver : BroadcastReceiver() {
147 
onReceivenull148     override fun onReceive(context: Context, intent: Intent?) {
149         if (DEBUG_HIBERNATION_POLICY) {
150             DumpableLog.i(LOG_TAG, "scheduleHibernationJob " +
151                     "with frequency ${getCheckFrequencyMs()}ms " +
152                     "and threshold ${getUnusedThresholdMs()}ms")
153         }
154 
155         val userManager = context.getSystemService(UserManager::class.java)!!
156         // If this user is a profile, then its hibernation/auto-revoke will be handled by the
157         // primary user
158         if (userManager.isProfile) {
159             if (DEBUG_HIBERNATION_POLICY) {
160                 DumpableLog.i(LOG_TAG, "user ${Process.myUserHandle().identifier} is a profile." +
161                         " Not running hibernation job.")
162             }
163             return
164         } else if (DEBUG_HIBERNATION_POLICY) {
165             DumpableLog.i(LOG_TAG, "user ${Process.myUserHandle().identifier} is a profile" +
166                     "owner. Running hibernation job.")
167         }
168 
169         if (isNewJobScheduleRequired(context)) {
170             // periodic jobs normally run immediately, which is unnecessarily premature
171             SKIP_NEXT_RUN = true
172             val jobInfo = JobInfo.Builder(
173                 Constants.HIBERNATION_JOB_ID,
174                 ComponentName(context, HibernationJobService::class.java))
175                 .setPeriodic(getCheckFrequencyMs())
176                 // persist this job across boots
177                 .setPersisted(true)
178                 .build()
179             val status = context.getSystemService(JobScheduler::class.java)!!.schedule(jobInfo)
180             if (status != JobScheduler.RESULT_SUCCESS) {
181                 DumpableLog.e(LOG_TAG,
182                     "Could not schedule ${HibernationJobService::class.java.simpleName}: $status")
183             }
184         }
185     }
186 
187     /**
188      * Returns whether a new job needs to be scheduled. A persisted job is used to keep the schedule
189      * across boots, but that job needs to be scheduled a first time and whenever the check
190      * frequency changes.
191      */
isNewJobScheduleRequirednull192     private fun isNewJobScheduleRequired(context: Context): Boolean {
193         // check if the job is already scheduled or needs a change
194         var scheduleNewJob = false
195         val existingJob: JobInfo? = context.getSystemService(JobScheduler::class.java)!!
196             .getPendingJob(Constants.HIBERNATION_JOB_ID)
197         if (existingJob == null) {
198             if (DEBUG_HIBERNATION_POLICY) {
199                 DumpableLog.i(LOG_TAG, "No existing job, scheduling a new one")
200             }
201             scheduleNewJob = true
202         } else if (existingJob.intervalMillis != getCheckFrequencyMs()) {
203             if (DEBUG_HIBERNATION_POLICY) {
204                 DumpableLog.i(LOG_TAG, "Interval frequency has changed, updating job")
205             }
206             scheduleNewJob = true
207         } else {
208             if (DEBUG_HIBERNATION_POLICY) {
209                 DumpableLog.i(LOG_TAG, "Job already scheduled.")
210             }
211         }
212         return scheduleNewJob
213     }
214 }
215 
216 /**
217  * Gets apps that are unused and should hibernate as a map of the user and their hibernateable apps.
218  */
219 @MainThread
getAppsToHibernatenull220 private suspend fun getAppsToHibernate(
221     context: Context
222 ): Map<UserHandle, List<LightPackageInfo>> {
223     if (!isHibernationJobEnabled()) {
224         return emptyMap()
225     }
226 
227     val now = System.currentTimeMillis()
228     val firstBootTime = context.firstBootTime
229 
230     // TODO ntmyren: remove once b/154796729 is fixed
231     Log.i(LOG_TAG, "getting UserPackageInfoLiveData for all users " +
232             "in " + HibernationJobService::class.java.simpleName)
233     val allPackagesByUser = AllPackageInfosLiveData.getInitializedValue(forceUpdate = true)
234     val allPackagesByUserByUid = allPackagesByUser.mapValues { (_, pkgs) ->
235         pkgs.groupBy { pkg -> pkg.uid }
236     }
237     val unusedApps = allPackagesByUser.toMutableMap()
238 
239     val userStats = UsageStatsLiveData[getUnusedThresholdMs(),
240         if (DEBUG_OVERRIDE_THRESHOLDS) INTERVAL_DAILY else INTERVAL_MONTHLY].getInitializedValue()
241     if (DEBUG_HIBERNATION_POLICY) {
242         for ((user, stats) in userStats) {
243             DumpableLog.i(LOG_TAG, "Usage stats for user ${user.identifier}: " +
244                     stats.map { stat ->
245                         stat.packageName to Date(stat.lastTimePackageUsed())
246                     }.toMap())
247         }
248     }
249     for (user in unusedApps.keys.toList()) {
250         if (user !in userStats.keys) {
251             if (DEBUG_HIBERNATION_POLICY) {
252                 DumpableLog.i(LOG_TAG, "Ignoring user ${user.identifier}")
253             }
254             unusedApps.remove(user)
255         }
256     }
257 
258     for ((user, stats) in userStats) {
259         var unusedUserApps = unusedApps[user] ?: continue
260 
261         unusedUserApps = unusedUserApps.filter { packageInfo ->
262             val pkgName = packageInfo.packageName
263 
264             val uidPackages = allPackagesByUserByUid[user]!![packageInfo.uid]
265                     ?.map { info -> info.packageName } ?: emptyList()
266             if (pkgName !in uidPackages) {
267                 Log.wtf(LOG_TAG, "Package $pkgName not among packages for " +
268                         "its uid ${packageInfo.uid}: $uidPackages")
269             }
270             var lastTimePkgUsed: Long = stats.lastTimePackageUsed(uidPackages)
271 
272             // Limit by install time
273             lastTimePkgUsed = Math.max(lastTimePkgUsed, packageInfo.firstInstallTime)
274 
275             // Limit by first boot time
276             lastTimePkgUsed = Math.max(lastTimePkgUsed, firstBootTime)
277 
278             // Handle cross-profile apps
279             if (context.isPackageCrossProfile(pkgName)) {
280                 for ((otherUser, otherStats) in userStats) {
281                     if (otherUser == user) {
282                         continue
283                     }
284                     lastTimePkgUsed =
285                         maxOf(lastTimePkgUsed, otherStats.lastTimePackageUsed(pkgName))
286                 }
287             }
288 
289             // Threshold check - whether app is unused
290             now - lastTimePkgUsed > getUnusedThresholdMs()
291         }
292 
293         unusedApps[user] = unusedUserApps
294         if (DEBUG_HIBERNATION_POLICY) {
295             DumpableLog.i(LOG_TAG, "Unused apps for user ${user.identifier}: " +
296                     "${unusedUserApps.map { it.packageName }}")
297         }
298     }
299 
300     val appsToHibernate = mutableMapOf<UserHandle, List<LightPackageInfo>>()
301     val userManager = context.getSystemService(UserManager::class.java)
302     for ((user, userApps) in unusedApps) {
303         if (userManager == null || !userManager.isUserUnlocked(user)) {
304             DumpableLog.w(LOG_TAG, "Skipping $user - locked direct boot state")
305             continue
306         }
307         var userAppsToHibernate = mutableListOf<LightPackageInfo>()
308         userApps.forEachInParallel(Main) { pkg: LightPackageInfo ->
309             if (isPackageHibernationExemptBySystem(pkg, user)) {
310                 return@forEachInParallel
311             }
312 
313             if (isPackageHibernationExemptByUser(context, pkg)) {
314                 return@forEachInParallel
315             }
316 
317             val packageName = pkg.packageName
318             val packageImportance = context
319                 .getSystemService(ActivityManager::class.java)!!
320                 .getPackageImportance(packageName)
321             if (packageImportance <= IMPORTANCE_CANT_SAVE_STATE) {
322                 // Process is running in a state where it should not be killed
323                 DumpableLog.i(LOG_TAG,
324                     "Skipping hibernation - $packageName running with importance " +
325                         "$packageImportance")
326                 return@forEachInParallel
327             }
328 
329             if (DEBUG_HIBERNATION_POLICY) {
330                 DumpableLog.i(LOG_TAG, "unused app $packageName - last used on " +
331                     userStats[user]?.lastTimePackageUsed(packageName)?.let(::Date))
332             }
333 
334             synchronized(userAppsToHibernate) {
335                 userAppsToHibernate.add(pkg)
336             }
337         }
338         appsToHibernate.put(user, userAppsToHibernate)
339     }
340     return appsToHibernate
341 }
342 
343 /**
344  * Gets the last time we consider the package used based off its usage stats. On pre-S devices
345  * this looks at last time visible which tracks explicit usage. In S, we add component usage
346  * which tracks various forms of implicit usage (e.g. service bindings).
347  */
UsageStatsnull348 fun UsageStats.lastTimePackageUsed(): Long {
349     var lastTimePkgUsed = this.lastTimeVisible
350     if (SdkLevel.isAtLeastS()) {
351         lastTimePkgUsed = maxOf(lastTimePkgUsed, this.lastTimeAnyComponentUsed)
352     }
353     return lastTimePkgUsed
354 }
355 
Listnull356 private fun List<UsageStats>.lastTimePackageUsed(pkgNames: List<String>): Long {
357     var result = 0L
358     for (stat in this) {
359         if (stat.packageName in pkgNames) {
360             result = Math.max(result, stat.lastTimePackageUsed())
361         }
362     }
363     return result
364 }
365 
Listnull366 private fun List<UsageStats>.lastTimePackageUsed(pkgName: String): Long {
367     return lastTimePackageUsed(listOf(pkgName))
368 }
369 
370 /**
371  * Checks if the given package is exempt from hibernation in a way that's not user-overridable
372  */
isPackageHibernationExemptBySystemnull373 suspend fun isPackageHibernationExemptBySystem(
374     pkg: LightPackageInfo,
375     user: UserHandle
376 ): Boolean {
377     if (!LauncherPackagesLiveData.getInitializedValue().contains(pkg.packageName)) {
378         if (DEBUG_HIBERNATION_POLICY) {
379             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - Package is not on launcher")
380         }
381         return true
382     }
383     if (!ExemptServicesLiveData[user]
384             .getInitializedValue()[pkg.packageName]
385             .isNullOrEmpty()) {
386         return true
387     }
388     if (Utils.isUserDisabledOrWorkProfile(user)) {
389         if (DEBUG_HIBERNATION_POLICY) {
390             DumpableLog.i(LOG_TAG,
391                     "Exempted ${pkg.packageName} - $user is disabled or a work profile")
392         }
393         return true
394     }
395     val carrierPrivilegedStatus = CarrierPrivilegedStatusLiveData[pkg.packageName]
396             .getInitializedValue()
397     if (carrierPrivilegedStatus != CARRIER_PRIVILEGE_STATUS_HAS_ACCESS &&
398             carrierPrivilegedStatus != CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
399         DumpableLog.w(LOG_TAG, "Error carrier privileged status for ${pkg.packageName}: " +
400                 carrierPrivilegedStatus)
401     }
402     if (carrierPrivilegedStatus == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
403         if (DEBUG_HIBERNATION_POLICY) {
404             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - carrier privileged")
405         }
406         return true
407     }
408 
409     if (PermissionControllerApplication.get()
410             .packageManager
411             .checkPermission(
412                     Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
413                     pkg.packageName) == PERMISSION_GRANTED) {
414         if (DEBUG_HIBERNATION_POLICY) {
415             DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} " +
416                     "- holder of READ_PRIVILEGED_PHONE_STATE")
417         }
418         return true
419     }
420 
421     if (SdkLevel.isAtLeastS()) {
422         val hasUpdatePackagesWithoutUserActionPermission =
423             PermissionControllerApplication.get().packageManager.checkPermission(
424                 UPDATE_PACKAGES_WITHOUT_USER_ACTION, pkg.packageName) == PERMISSION_GRANTED
425         val installPackagesAppOpMode = AppOpLiveData[pkg.packageName,
426             AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, pkg.uid]
427             .getInitializedValue()
428         if (hasUpdatePackagesWithoutUserActionPermission &&
429             installPackagesAppOpMode == AppOpsManager.MODE_ALLOWED) {
430             if (DEBUG_HIBERNATION_POLICY) {
431                 DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - 3p app store")
432             }
433             return true
434         }
435     }
436 
437     return false
438 }
439 
440 /**
441  * Checks if the given package is exempt from hibernation/auto revoke in a way that's
442  * user-overridable
443  */
isPackageHibernationExemptByUsernull444 suspend fun isPackageHibernationExemptByUser(
445     context: Context,
446     pkg: LightPackageInfo
447 ): Boolean {
448     val packageName = pkg.packageName
449     val packageUid = pkg.uid
450 
451     val allowlistAppOpMode =
452         AppOpLiveData[packageName,
453             AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid]
454             .getInitializedValue()
455     if (allowlistAppOpMode == AppOpsManager.MODE_DEFAULT) {
456         // Initial state - allowlist not explicitly overridden by either user or installer
457         if (DEBUG_OVERRIDE_THRESHOLDS) {
458             // Suppress exemptions to allow debugging
459             return false
460         }
461 
462         if (hibernationTargetsPreSApps()) {
463             // Default on if overridden
464             return false
465         }
466 
467         // Q- packages exempt by default, except R- on Auto since Auto-Revoke was skipped in R
468         val maxTargetSdkVersionForExemptApps =
469                 if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
470                     android.os.Build.VERSION_CODES.R
471                 } else {
472                     android.os.Build.VERSION_CODES.Q
473                 }
474 
475         return pkg.targetSdkVersion <= maxTargetSdkVersionForExemptApps
476     }
477     // Check whether user/installer exempt
478     return allowlistAppOpMode != AppOpsManager.MODE_ALLOWED
479 }
480 
Contextnull481 private fun Context.isPackageCrossProfile(pkg: String): Boolean {
482     return packageManager.checkPermission(
483         Manifest.permission.INTERACT_ACROSS_PROFILES, pkg) == PERMISSION_GRANTED ||
484         packageManager.checkPermission(
485             Manifest.permission.INTERACT_ACROSS_USERS, pkg) == PERMISSION_GRANTED ||
486         packageManager.checkPermission(
487             Manifest.permission.INTERACT_ACROSS_USERS_FULL, pkg) == PERMISSION_GRANTED
488 }
489 
490 val Context.sharedPreferences: SharedPreferences
491     get() {
492     return PreferenceManager.getDefaultSharedPreferences(this)
493 }
494 
495 private val Context.firstBootTime: Long get() {
496     var time = sharedPreferences.getLong(PREF_KEY_FIRST_BOOT_TIME, -1L)
497     if (time > 0) {
498         return time
499     }
500     // This is the first boot
501     time = System.currentTimeMillis()
502     sharedPreferences.edit().putLong(PREF_KEY_FIRST_BOOT_TIME, time).apply()
503     return time
504 }
505 
506 /**
507  * A job to check for apps unused in the last [getUnusedThresholdMs]ms every
508  * [getCheckFrequencyMs]ms and hibernate the app / revoke their runtime permissions.
509  */
510 class HibernationJobService : JobService() {
511     var job: Job? = null
512     var jobStartTime: Long = -1L
513 
onStartJobnull514     override fun onStartJob(params: JobParameters?): Boolean {
515         if (DEBUG_HIBERNATION_POLICY) {
516             DumpableLog.i(LOG_TAG, "onStartJob")
517         }
518 
519         if (SKIP_NEXT_RUN) {
520             SKIP_NEXT_RUN = false
521             if (DEBUG_HIBERNATION_POLICY) {
522                 DumpableLog.i(LOG_TAG, "Skipping auto revoke first run when scheduled by system")
523             }
524             jobFinished(params, false)
525             return true
526         }
527 
528         jobStartTime = System.currentTimeMillis()
529         job = GlobalScope.launch(Main) {
530             try {
531                 var sessionId = Constants.INVALID_SESSION_ID
532                 while (sessionId == Constants.INVALID_SESSION_ID) {
533                     sessionId = Random().nextLong()
534                 }
535 
536                 val appsToHibernate = getAppsToHibernate(this@HibernationJobService)
537                 var hibernatedApps: Set<Pair<String, UserHandle>> = emptySet()
538                 if (isHibernationEnabled()) {
539                     val hibernationController =
540                         HibernationController(this@HibernationJobService, getUnusedThresholdMs(),
541                             hibernationTargetsPreSApps())
542                     hibernatedApps = hibernationController.hibernateApps(appsToHibernate)
543                 }
544                 val revokedApps = revokeAppPermissions(
545                         appsToHibernate, this@HibernationJobService, sessionId)
546                 val unusedApps: Set<Pair<String, UserHandle>> = hibernatedApps + revokedApps
547                 if (unusedApps.isNotEmpty()) {
548                     showUnusedAppsNotification(unusedApps.size, sessionId)
549                 }
550             } catch (e: Exception) {
551                 DumpableLog.e(LOG_TAG, "Failed to auto-revoke permissions", e)
552             }
553             jobFinished(params, false)
554         }
555         return true
556     }
557 
showUnusedAppsNotificationnull558     private suspend fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) {
559         val notificationManager = getSystemService(NotificationManager::class.java)!!
560 
561         val permissionReminderChannel = NotificationChannel(
562                 Constants.PERMISSION_REMINDER_CHANNEL_ID, getString(R.string.permission_reminders),
563                 NotificationManager.IMPORTANCE_LOW)
564         notificationManager.createNotificationChannel(permissionReminderChannel)
565 
566         val clickIntent = Intent(Intent.ACTION_MANAGE_UNUSED_APPS).apply {
567             putExtra(Constants.EXTRA_SESSION_ID, sessionId)
568             flags = Intent.FLAG_ACTIVITY_NEW_TASK
569         }
570         val pendingIntent = PendingIntent.getActivity(this, 0, clickIntent,
571                 PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT)
572 
573         var notifTitle: String
574         var notifContent: String
575         if (isHibernationEnabled()) {
576             notifTitle = getResources().getQuantityString(
577                 R.plurals.unused_apps_notification_title, numUnused, numUnused)
578             notifContent = getString(R.string.unused_apps_notification_content)
579         } else {
580             notifTitle = getString(R.string.auto_revoke_permission_notification_title)
581             notifContent = getString(R.string.auto_revoke_permission_notification_content)
582         }
583 
584         val b = Notification.Builder(this, Constants.PERMISSION_REMINDER_CHANNEL_ID)
585             .setContentTitle(notifTitle)
586             .setContentText(notifContent)
587             .setStyle(Notification.BigTextStyle().bigText(notifContent))
588             .setSmallIcon(R.drawable.ic_settings_24dp)
589             .setColor(getColor(android.R.color.system_notification_accent_color))
590             .setAutoCancel(true)
591             .setContentIntent(pendingIntent)
592             .extend(Notification.TvExtender())
593         Utils.getSettingsLabelForNotifications(applicationContext.packageManager)?.let {
594             settingsLabel ->
595             val extras = Bundle()
596             extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, settingsLabel.toString())
597             b.addExtras(extras)
598         }
599 
600         notificationManager.notify(HibernationJobService::class.java.simpleName,
601                 Constants.UNUSED_APPS_NOTIFICATION_ID, b.build())
602         // Preload the unused packages
603         getUnusedPackages().getInitializedValue()
604     }
605 
onStopJobnull606     override fun onStopJob(params: JobParameters?): Boolean {
607         DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
608         job?.cancel()
609         return true
610     }
611 }
612 
613 /**
614  * Packages using exempt services for the current user (package-name -> list<service-interfaces>
615  * implemented by the package)
616  */
617 class ExemptServicesLiveData(val user: UserHandle)
618     : SmartUpdateMediatorLiveData<Map<String, List<String>>>() {
619     private val serviceLiveDatas: List<SmartUpdateMediatorLiveData<Set<String>>> = listOf(
620             ServiceLiveData[InputMethod.SERVICE_INTERFACE,
621                     Manifest.permission.BIND_INPUT_METHOD,
622                     user],
623             ServiceLiveData[
624                     NotificationListenerService.SERVICE_INTERFACE,
625                     Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE,
626                     user],
627             ServiceLiveData[
628                     AccessibilityService.SERVICE_INTERFACE,
629                     Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
630                     user],
631             ServiceLiveData[
632                     WallpaperService.SERVICE_INTERFACE,
633                     Manifest.permission.BIND_WALLPAPER,
634                     user],
635             ServiceLiveData[
636                     VoiceInteractionService.SERVICE_INTERFACE,
637                     Manifest.permission.BIND_VOICE_INTERACTION,
638                     user],
639             ServiceLiveData[
640                     PrintService.SERVICE_INTERFACE,
641                     Manifest.permission.BIND_PRINT_SERVICE,
642                     user],
643             ServiceLiveData[
644                     DreamService.SERVICE_INTERFACE,
645                     Manifest.permission.BIND_DREAM_SERVICE,
646                     user],
647             ServiceLiveData[
648                     AutofillService.SERVICE_INTERFACE,
649                     Manifest.permission.BIND_AUTOFILL_SERVICE,
650                     user],
651             ServiceLiveData[
652                     DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE,
653                     Manifest.permission.BIND_DEVICE_ADMIN,
654                     user],
655             BroadcastReceiverLiveData[
656                     DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
657                     Manifest.permission.BIND_DEVICE_ADMIN,
658                     user]
659     )
660 
661     init {
<lambda>null662         serviceLiveDatas.forEach { addSource(it) { update() } }
663     }
664 
onUpdatenull665     override fun onUpdate() {
666         if (serviceLiveDatas.all { it.isInitialized }) {
667             val pksToServices = mutableMapOf<String, MutableList<String>>()
668 
669             serviceLiveDatas.forEach { serviceLD ->
670                 serviceLD.value!!.forEach { packageName ->
671                     pksToServices.getOrPut(packageName, { mutableListOf() })
672                             .add((serviceLD as? HasIntentAction)?.intentAction ?: "???")
673                 }
674             }
675 
676             value = pksToServices
677         }
678     }
679 
680     /**
681      * Repository for ExemptServiceLiveData
682      *
683      * <p> Key value is user
684      */
685     companion object : DataRepositoryForPackage<UserHandle, ExemptServicesLiveData>() {
newValuenull686         override fun newValue(key: UserHandle): ExemptServicesLiveData {
687             return ExemptServicesLiveData(key)
688         }
689     }
690 }
691 
692 /**
693  * Live data for whether the hibernation feature is enabled or not.
694  */
695 object HibernationEnabledLiveData
696     : MutableLiveData<Boolean>() {
697     init {
698         value = SdkLevel.isAtLeastS() &&
699             DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
700             Utils.PROPERTY_APP_HIBERNATION_ENABLED, true /* defaultValue */)
701         DeviceConfig.addOnPropertiesChangedListener(
702             NAMESPACE_APP_HIBERNATION,
703             PermissionControllerApplication.get().mainExecutor,
propertiesnull704             { properties ->
705                 for (key in properties.keyset) {
706                     if (key == Utils.PROPERTY_APP_HIBERNATION_ENABLED) {
707                         value = SdkLevel.isAtLeastS() &&
708                             properties.getBoolean(key, true /* defaultValue */)
709                         break
710                     }
711                 }
712             }
713         )
714     }
715 }
716