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