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