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