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
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 
114 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             true /* 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  */
131 fun hibernationTargetsPreSApps(): Boolean {
132     return DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
133         Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS,
134         false /* defaultValue */)
135 }
136 
137 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 
148     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      */
192     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
220 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  */
348 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 
356 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 
366 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  */
373 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  */
444 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 
481 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 
514     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 
558     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 or
572             PendingIntent.FLAG_IMMUTABLE)
573 
574         var notifTitle: String
575         var notifContent: String
576         if (isHibernationEnabled()) {
577             notifTitle = getResources().getQuantityString(
578                 R.plurals.unused_apps_notification_title, numUnused, numUnused)
579             notifContent = getString(R.string.unused_apps_notification_content)
580         } else {
581             notifTitle = getString(R.string.auto_revoke_permission_notification_title)
582             notifContent = getString(R.string.auto_revoke_permission_notification_content)
583         }
584 
585         val b = Notification.Builder(this, Constants.PERMISSION_REMINDER_CHANNEL_ID)
586             .setContentTitle(notifTitle)
587             .setContentText(notifContent)
588             .setStyle(Notification.BigTextStyle().bigText(notifContent))
589             .setSmallIcon(R.drawable.ic_settings_24dp)
590             .setColor(getColor(android.R.color.system_notification_accent_color))
591             .setAutoCancel(true)
592             .setContentIntent(pendingIntent)
593             .extend(Notification.TvExtender())
594         Utils.getSettingsLabelForNotifications(applicationContext.packageManager)?.let {
595             settingsLabel ->
596             val extras = Bundle()
597             extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, settingsLabel.toString())
598             b.addExtras(extras)
599         }
600 
601         notificationManager.notify(HibernationJobService::class.java.simpleName,
602                 Constants.UNUSED_APPS_NOTIFICATION_ID, b.build())
603         // Preload the unused packages
604         getUnusedPackages().getInitializedValue()
605     }
606 
607     override fun onStopJob(params: JobParameters?): Boolean {
608         DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
609         job?.cancel()
610         return true
611     }
612 }
613 
614 /**
615  * Packages using exempt services for the current user (package-name -> list<service-interfaces>
616  * implemented by the package)
617  */
618 class ExemptServicesLiveData(val user: UserHandle)
619     : SmartUpdateMediatorLiveData<Map<String, List<String>>>() {
620     private val serviceLiveDatas: List<SmartUpdateMediatorLiveData<Set<String>>> = listOf(
621             ServiceLiveData[InputMethod.SERVICE_INTERFACE,
622                     Manifest.permission.BIND_INPUT_METHOD,
623                     user],
624             ServiceLiveData[
625                     NotificationListenerService.SERVICE_INTERFACE,
626                     Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE,
627                     user],
628             ServiceLiveData[
629                     AccessibilityService.SERVICE_INTERFACE,
630                     Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
631                     user],
632             ServiceLiveData[
633                     WallpaperService.SERVICE_INTERFACE,
634                     Manifest.permission.BIND_WALLPAPER,
635                     user],
636             ServiceLiveData[
637                     VoiceInteractionService.SERVICE_INTERFACE,
638                     Manifest.permission.BIND_VOICE_INTERACTION,
639                     user],
640             ServiceLiveData[
641                     PrintService.SERVICE_INTERFACE,
642                     Manifest.permission.BIND_PRINT_SERVICE,
643                     user],
644             ServiceLiveData[
645                     DreamService.SERVICE_INTERFACE,
646                     Manifest.permission.BIND_DREAM_SERVICE,
647                     user],
648             ServiceLiveData[
649                     AutofillService.SERVICE_INTERFACE,
650                     Manifest.permission.BIND_AUTOFILL_SERVICE,
651                     user],
652             ServiceLiveData[
653                     DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE,
654                     Manifest.permission.BIND_DEVICE_ADMIN,
655                     user],
656             BroadcastReceiverLiveData[
657                     DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
658                     Manifest.permission.BIND_DEVICE_ADMIN,
659                     user]
660     )
661 
662     init {
663         serviceLiveDatas.forEach { addSource(it) { update() } }
664     }
665 
666     override fun onUpdate() {
667         if (serviceLiveDatas.all { it.isInitialized }) {
668             val pksToServices = mutableMapOf<String, MutableList<String>>()
669 
670             serviceLiveDatas.forEach { serviceLD ->
671                 serviceLD.value!!.forEach { packageName ->
672                     pksToServices.getOrPut(packageName, { mutableListOf() })
673                             .add((serviceLD as? HasIntentAction)?.intentAction ?: "???")
674                 }
675             }
676 
677             value = pksToServices
678         }
679     }
680 
681     /**
682      * Repository for ExemptServiceLiveData
683      *
684      * <p> Key value is user
685      */
686     companion object : DataRepositoryForPackage<UserHandle, ExemptServicesLiveData>() {
687         override fun newValue(key: UserHandle): ExemptServicesLiveData {
688             return ExemptServicesLiveData(key)
689         }
690     }
691 }
692 
693 /**
694  * Live data for whether the hibernation feature is enabled or not.
695  */
696 object HibernationEnabledLiveData
697     : MutableLiveData<Boolean>() {
698     init {
699         value = SdkLevel.isAtLeastS() &&
700             DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION,
701             Utils.PROPERTY_APP_HIBERNATION_ENABLED, true /* defaultValue */)
702         DeviceConfig.addOnPropertiesChangedListener(
703             NAMESPACE_APP_HIBERNATION,
704             PermissionControllerApplication.get().mainExecutor,
705             { properties ->
706                 for (key in properties.keyset) {
707                     if (key == Utils.PROPERTY_APP_HIBERNATION_ENABLED) {
708                         value = SdkLevel.isAtLeastS() &&
709                             properties.getBoolean(key, true /* defaultValue */)
710                         break
711                     }
712                 }
713             }
714         )
715     }
716 }
717