1 /*
<lambda>null2 * Copyright (C) 2020 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 @file:JvmName("AutoRevokePermissions")
18
19 package com.android.permissioncontroller.permission.service
20
21 import android.Manifest
22 import android.accessibilityservice.AccessibilityService
23 import android.app.ActivityManager
24 import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
25 import android.app.AppOpsManager
26 import android.app.AppOpsManager.MODE_ALLOWED
27 import android.app.AppOpsManager.MODE_DEFAULT
28 import android.app.Notification
29 import android.app.NotificationChannel
30 import android.app.NotificationManager
31 import android.app.NotificationManager.IMPORTANCE_LOW
32 import android.app.PendingIntent
33 import android.app.PendingIntent.FLAG_ONE_SHOT
34 import android.app.PendingIntent.FLAG_UPDATE_CURRENT
35 import android.app.admin.DeviceAdminReceiver
36 import android.app.admin.DevicePolicyManager
37 import android.app.job.JobInfo
38 import android.app.job.JobParameters
39 import android.app.job.JobScheduler
40 import android.app.job.JobService
41 import android.app.usage.UsageStats
42 import android.app.usage.UsageStatsManager.INTERVAL_DAILY
43 import android.app.usage.UsageStatsManager.INTERVAL_MONTHLY
44 import android.content.BroadcastReceiver
45 import android.content.ComponentName
46 import android.content.Context
47 import android.content.Intent
48 import android.content.SharedPreferences
49 import android.content.pm.PackageManager
50 import android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED
51 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
52 import android.content.pm.PackageManager.PERMISSION_GRANTED
53 import android.net.NetworkScoreManager
54 import android.os.Bundle
55 import android.os.Process.myUserHandle
56 import android.os.UserHandle
57 import android.os.UserManager
58 import android.printservice.PrintService
59 import android.provider.DeviceConfig
60 import android.provider.Settings
61 import android.service.attention.AttentionService
62 import android.service.autofill.AutofillService
63 import android.service.autofill.augmented.AugmentedAutofillService
64 import android.service.dreams.DreamService
65 import android.service.notification.NotificationListenerService
66 import android.service.textclassifier.TextClassifierService
67 import android.service.voice.VoiceInteractionService
68 import android.service.wallpaper.WallpaperService
69 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
70 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS
71 import android.util.Log
72 import android.view.inputmethod.InputMethod
73 import androidx.annotation.MainThread
74 import androidx.preference.PreferenceManager
75 import com.android.permissioncontroller.Constants
76 import com.android.permissioncontroller.Constants.ACTION_MANAGE_AUTO_REVOKE
77 import com.android.permissioncontroller.Constants.AUTO_REVOKE_NOTIFICATION_ID
78 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
79 import com.android.permissioncontroller.Constants.INVALID_SESSION_ID
80 import com.android.permissioncontroller.Constants.PERMISSION_REMINDER_CHANNEL_ID
81 import com.android.permissioncontroller.DumpableLog
82 import com.android.permissioncontroller.PermissionControllerStatsLog
83 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED
84 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_UNUSED_APP_PERMISSION_REVOKED
85 import com.android.permissioncontroller.R
86 import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
87 import com.android.permissioncontroller.permission.data.AppOpLiveData
88 import com.android.permissioncontroller.permission.data.BroadcastReceiverLiveData
89 import com.android.permissioncontroller.permission.data.CarrierPrivilegedStatusLiveData
90 import com.android.permissioncontroller.permission.data.DataRepositoryForPackage
91 import com.android.permissioncontroller.permission.data.HasIntentAction
92 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
93 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
94 import com.android.permissioncontroller.permission.data.ServiceLiveData
95 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
96 import com.android.permissioncontroller.permission.data.UnusedAutoRevokedPackagesLiveData
97 import com.android.permissioncontroller.permission.data.UsageStatsLiveData
98 import com.android.permissioncontroller.permission.data.UsersLiveData
99 import com.android.permissioncontroller.permission.data.get
100 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
101 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
102 import com.android.permissioncontroller.permission.service.AutoRevokePermissionsProto.AutoRevokePermissionsDumpProto
103 import com.android.permissioncontroller.permission.service.AutoRevokePermissionsProto.PackageProto
104 import com.android.permissioncontroller.permission.service.AutoRevokePermissionsProto.PerUserProto
105 import com.android.permissioncontroller.permission.service.AutoRevokePermissionsProto.PermissionGroupProto
106 import com.android.permissioncontroller.permission.service.AutoRevokePermissionsProto.TeamFoodSettingsProto
107 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
108 import com.android.permissioncontroller.permission.utils.IPC
109 import com.android.permissioncontroller.permission.utils.KotlinUtils
110 import com.android.permissioncontroller.permission.utils.Utils
111 import com.android.permissioncontroller.permission.utils.Utils.PROPERTY_AUTO_REVOKE_CHECK_FREQUENCY_MILLIS
112 import com.android.permissioncontroller.permission.utils.Utils.PROPERTY_AUTO_REVOKE_UNUSED_THRESHOLD_MILLIS
113 import com.android.permissioncontroller.permission.utils.application
114 import com.android.permissioncontroller.permission.utils.forEachInParallel
115 import com.android.permissioncontroller.permission.utils.updatePermissionFlags
116 import kotlinx.coroutines.Dispatchers.Main
117 import kotlinx.coroutines.GlobalScope
118 import kotlinx.coroutines.Job
119 import kotlinx.coroutines.async
120 import kotlinx.coroutines.launch
121 import java.util.Date
122 import java.util.Random
123 import java.util.concurrent.TimeUnit.DAYS
124 import java.util.concurrent.TimeUnit.SECONDS
125 import java.util.concurrent.atomic.AtomicBoolean
126
127 private const val LOG_TAG = "AutoRevokePermissions"
128 private const val DEBUG_OVERRIDE_THRESHOLDS = false
129 // TODO eugenesusla: temporarily enabled for extra logs during dogfooding
130 private const val DEBUG = true || DEBUG_OVERRIDE_THRESHOLDS
131
132 private const val AUTO_REVOKE_ENABLED = true
133
134 private var SKIP_NEXT_RUN = false
135
136 private val EXEMPT_PERMISSIONS = listOf(
137 android.Manifest.permission.ACTIVITY_RECOGNITION)
138
139 private val DEFAULT_UNUSED_THRESHOLD_MS =
140 if (AUTO_REVOKE_ENABLED) DAYS.toMillis(90) else Long.MAX_VALUE
141 fun getUnusedThresholdMs(context: Context) = when {
142 DEBUG_OVERRIDE_THRESHOLDS -> SECONDS.toMillis(1)
143 TeamfoodSettings.get(context) != null -> TeamfoodSettings.get(context)!!.unusedThresholdMs
144 else -> DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
145 PROPERTY_AUTO_REVOKE_UNUSED_THRESHOLD_MILLIS,
146 DEFAULT_UNUSED_THRESHOLD_MS)
147 }
148
149 private val DEFAULT_CHECK_FREQUENCY_MS = DAYS.toMillis(15)
getCheckFrequencyMsnull150 private fun getCheckFrequencyMs(context: Context) = when {
151 TeamfoodSettings.get(context) != null -> TeamfoodSettings.get(context)!!.checkFrequencyMs
152 else -> DeviceConfig.getLong(
153 DeviceConfig.NAMESPACE_PERMISSIONS,
154 PROPERTY_AUTO_REVOKE_CHECK_FREQUENCY_MILLIS,
155 DEFAULT_CHECK_FREQUENCY_MS)
156 }
157
158 private val SERVER_LOG_ID =
159 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_UNUSED_APP_PERMISSION_REVOKED
160
161 private val PREF_KEY_FIRST_BOOT_TIME = "first_boot_time"
162
isAutoRevokeEnablednull163 fun isAutoRevokeEnabled(context: Context): Boolean {
164 return getCheckFrequencyMs(context) > 0 &&
165 getUnusedThresholdMs(context) > 0 &&
166 getUnusedThresholdMs(context) != Long.MAX_VALUE
167 }
168
169 /**
170 * @return dump of auto revoke service as a proto
171 */
dumpAutoRevokePermissionsnull172 suspend fun dumpAutoRevokePermissions(context: Context): AutoRevokePermissionsDumpProto {
173 val teamFoodSettings = GlobalScope.async(IPC) {
174 TeamfoodSettings.get(context)?.dump()
175 ?: TeamFoodSettingsProto.newBuilder().build()
176 }
177
178 val dumpData = GlobalScope.async(IPC) {
179 AutoRevokeDumpLiveData(context).getInitializedValue()
180 }
181
182 return AutoRevokePermissionsDumpProto.newBuilder()
183 .setTeamfoodSettings(teamFoodSettings.await())
184 .addAllUsers(dumpData.await().dumpUsers())
185 .build()
186 }
187
188 /**
189 * Receiver of the onBoot event.
190 */
191 class AutoRevokeOnBootReceiver : BroadcastReceiver() {
192
onReceivenull193 override fun onReceive(context: Context, intent: Intent?) {
194
195 // Auto-revoke is not enabled on Automotive devices
196 if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
197 DumpableLog.i(LOG_TAG, "Auto-revoke not scheduled on Automotive devices")
198 return
199 }
200
201 // Init firstBootTime
202 val firstBootTime = context.firstBootTime
203
204 if (DEBUG) {
205 DumpableLog.i(LOG_TAG, "scheduleAutoRevokePermissions " +
206 "with frequency ${getCheckFrequencyMs(context)}ms " +
207 "and threshold ${getUnusedThresholdMs(context)}ms")
208 }
209
210 val userManager = context.getSystemService(UserManager::class.java)!!
211 // If this user is a profile, then its auto revoke will be handled by the primary user
212 if (userManager.isProfile) {
213 if (DEBUG) {
214 DumpableLog.i(LOG_TAG, "user ${myUserHandle().identifier} is a profile. Not " +
215 "running Auto Revoke.")
216 }
217 return
218 } else if (DEBUG) {
219 DumpableLog.i(LOG_TAG, "user ${myUserHandle().identifier} is a profile owner. " +
220 "Running Auto Revoke.")
221 }
222
223 SKIP_NEXT_RUN = true
224
225 val jobInfo = JobInfo.Builder(
226 Constants.AUTO_REVOKE_JOB_ID,
227 ComponentName(context, AutoRevokeService::class.java))
228 .setPeriodic(getCheckFrequencyMs(context))
229 .build()
230 val status = context.getSystemService(JobScheduler::class.java)!!.schedule(jobInfo)
231 if (status != JobScheduler.RESULT_SUCCESS) {
232 DumpableLog.e(LOG_TAG,
233 "Could not schedule ${AutoRevokeService::class.java.simpleName}: $status")
234 }
235 }
236 }
237
238 @MainThread
revokePermissionsOnUnusedAppsnull239 private suspend fun revokePermissionsOnUnusedApps(
240 context: Context,
241 sessionId: Long = INVALID_SESSION_ID
242 ):
243 List<Pair<String, UserHandle>> {
244 if (!isAutoRevokeEnabled(context)) {
245 return emptyList()
246 }
247
248 val now = System.currentTimeMillis()
249 val firstBootTime = context.firstBootTime
250
251 // TODO ntmyren: remove once b/154796729 is fixed
252 Log.i(LOG_TAG, "getting UserPackageInfoLiveData for all users " +
253 "in AutoRevokePermissions")
254 val allPackagesByUser = AllPackageInfosLiveData.getInitializedValue()
255 val allPackagesByUserByUid = allPackagesByUser.mapValues { (_, pkgs) ->
256 pkgs.groupBy { pkg -> pkg.uid }
257 }
258 val unusedApps = allPackagesByUser.toMutableMap()
259
260 val userStats = UsageStatsLiveData[getUnusedThresholdMs(context),
261 if (DEBUG_OVERRIDE_THRESHOLDS) INTERVAL_DAILY else INTERVAL_MONTHLY].getInitializedValue()
262 if (DEBUG) {
263 for ((user, stats) in userStats) {
264 DumpableLog.i(LOG_TAG, "Usage stats for user ${user.identifier}: " +
265 stats.map { stat ->
266 stat.packageName to Date(stat.lastTimeVisible)
267 }.toMap())
268 }
269 }
270 for (user in unusedApps.keys.toList()) {
271 if (user !in userStats.keys) {
272 if (DEBUG) {
273 DumpableLog.i(LOG_TAG, "Ignoring user ${user.identifier}")
274 }
275 unusedApps.remove(user)
276 }
277 }
278
279 for ((user, stats) in userStats) {
280 var unusedUserApps = unusedApps[user] ?: continue
281
282 unusedUserApps = unusedUserApps.filter { packageInfo ->
283 val pkgName = packageInfo.packageName
284
285 val uidPackages = allPackagesByUserByUid[user]!![packageInfo.uid]
286 ?.map { info -> info.packageName } ?: emptyList()
287 if (pkgName !in uidPackages) {
288 Log.wtf(LOG_TAG, "Package $pkgName not among packages for " +
289 "its uid ${packageInfo.uid}: $uidPackages")
290 }
291 var lastTimeVisible: Long = stats.lastTimeVisible(uidPackages)
292
293 // Limit by install time
294 lastTimeVisible = Math.max(lastTimeVisible, packageInfo.firstInstallTime)
295
296 // Limit by first boot time
297 lastTimeVisible = Math.max(lastTimeVisible, firstBootTime)
298
299 // Handle cross-profile apps
300 if (context.isPackageCrossProfile(pkgName)) {
301 for ((otherUser, otherStats) in userStats) {
302 if (otherUser == user) {
303 continue
304 }
305 lastTimeVisible = Math.max(lastTimeVisible, otherStats.lastTimeVisible(pkgName))
306 }
307 }
308
309 // Threshold check - whether app is unused
310 now - lastTimeVisible > getUnusedThresholdMs(context)
311 }
312
313 unusedApps[user] = unusedUserApps
314 if (DEBUG) {
315 DumpableLog.i(LOG_TAG, "Unused apps for user ${user.identifier}: " +
316 "${unusedUserApps.map { it.packageName }}")
317 }
318 }
319
320 val revokedApps = mutableListOf<Pair<String, UserHandle>>()
321 val userManager = context.getSystemService(UserManager::class.java)
322 for ((user, userApps) in unusedApps) {
323 if (userManager == null || !userManager.isUserUnlocked(user)) {
324 DumpableLog.w(LOG_TAG, "Skipping $user - locked direct boot state")
325 continue
326 }
327 userApps.forEachInParallel(Main) { pkg: LightPackageInfo ->
328 if (pkg.grantedPermissions.isEmpty()) {
329 return@forEachInParallel
330 }
331
332 if (isPackageAutoRevokePermanentlyExempt(pkg, user)) {
333 return@forEachInParallel
334 }
335
336 val packageName = pkg.packageName
337 if (isPackageAutoRevokeExempt(context, pkg)) {
338 return@forEachInParallel
339 }
340
341 val anyPermsRevoked = AtomicBoolean(false)
342 val pkgPermGroups: Map<String, List<String>>? =
343 PackagePermissionsLiveData[packageName, user]
344 .getInitializedValue()
345
346 pkgPermGroups?.entries?.forEachInParallel(Main) { (groupName, _) ->
347 if (groupName == PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS) {
348 return@forEachInParallel
349 }
350
351 val group: LightAppPermGroup =
352 LightAppPermGroupLiveData[packageName, groupName, user]
353 .getInitializedValue()
354 ?: return@forEachInParallel
355
356 val fixed = group.isBackgroundFixed || group.isForegroundFixed
357 val granted = group.permissions.any { (_, perm) ->
358 perm.isGrantedIncludingAppOp && perm.name !in EXEMPT_PERMISSIONS
359 }
360 if (!fixed &&
361 granted &&
362 !group.isGrantedByDefault &&
363 !group.isGrantedByRole &&
364 group.isUserSensitive) {
365
366 val revocablePermissions = group.permissions.keys.toList()
367
368 if (revocablePermissions.isEmpty()) {
369 return@forEachInParallel
370 }
371
372 if (DEBUG) {
373 DumpableLog.i(LOG_TAG, "revokeUnused $packageName - $revocablePermissions" +
374 " - lastVisible on " +
375 userStats[user]?.lastTimeVisible(packageName)?.let(::Date))
376 }
377
378 val uid = group.packageInfo.uid
379 for (permName in revocablePermissions) {
380 PermissionControllerStatsLog.write(
381 PERMISSION_GRANT_REQUEST_RESULT_REPORTED,
382 sessionId, uid, packageName, permName, false, SERVER_LOG_ID)
383 }
384
385 val packageImportance = context
386 .getSystemService(ActivityManager::class.java)!!
387 .getPackageImportance(packageName)
388 if (packageImportance > IMPORTANCE_TOP_SLEEPING) {
389 if (DEBUG) {
390 DumpableLog.i(LOG_TAG, "revoking $packageName - $revocablePermissions")
391 DumpableLog.i(LOG_TAG, "State pre revocation: ${group.allPermissions}")
392 }
393 anyPermsRevoked.compareAndSet(false, true)
394
395 val bgRevokedState = KotlinUtils.revokeBackgroundRuntimePermissions(
396 context.application, group,
397 userFixed = false, oneTime = false,
398 filterPermissions = revocablePermissions)
399 if (DEBUG) {
400 DumpableLog.i(LOG_TAG,
401 "Bg state post revocation: ${bgRevokedState.allPermissions}")
402 }
403 val fgRevokedState = KotlinUtils.revokeForegroundRuntimePermissions(
404 context.application, group,
405 userFixed = false, oneTime = false,
406 filterPermissions = revocablePermissions)
407 if (DEBUG) {
408 DumpableLog.i(LOG_TAG,
409 "Fg state post revocation: ${fgRevokedState.allPermissions}")
410 }
411
412 for (permission in revocablePermissions) {
413 context.packageManager.updatePermissionFlags(
414 permission, packageName, user,
415 FLAG_PERMISSION_AUTO_REVOKED to true,
416 FLAG_PERMISSION_USER_SET to false)
417 }
418 } else {
419 DumpableLog.i(LOG_TAG,
420 "Skipping auto-revoke - $packageName running with importance " +
421 "$packageImportance")
422 }
423 }
424 }
425
426 if (anyPermsRevoked.get()) {
427 synchronized(revokedApps) {
428 revokedApps.add(pkg.packageName to user)
429 }
430 }
431 }
432 if (DEBUG) {
433 synchronized(revokedApps) {
434 DumpableLog.i(LOG_TAG,
435 "Done auto-revoke for user ${user.identifier} - revoked $revokedApps")
436 }
437 }
438 }
439 return revokedApps
440 }
441
Listnull442 private fun List<UsageStats>.lastTimeVisible(pkgNames: List<String>): Long {
443 var result = 0L
444 for (stat in this) {
445 if (stat.packageName in pkgNames) {
446 result = Math.max(result, stat.lastTimeVisible)
447 }
448 }
449 return result
450 }
451
Listnull452 private fun List<UsageStats>.lastTimeVisible(pkgName: String): Long {
453 return lastTimeVisible(listOf(pkgName))
454 }
455
456 /**
457 * Checks if the given package is exempt from auto revoke in a way that's not user-overridable
458 */
isPackageAutoRevokePermanentlyExemptnull459 suspend fun isPackageAutoRevokePermanentlyExempt(
460 pkg: LightPackageInfo,
461 user: UserHandle
462 ): Boolean {
463 if (!ExemptServicesLiveData[user]
464 .getInitializedValue()[pkg.packageName]
465 .isNullOrEmpty()) {
466 return true
467 }
468 if (Utils.isUserDisabledOrWorkProfile(user)) {
469 if (DEBUG) {
470 DumpableLog.i(LOG_TAG,
471 "Exempted ${pkg.packageName} - $user is disabled or a work profile")
472 }
473 return true
474 }
475 val carrierPrivilegedStatus = CarrierPrivilegedStatusLiveData[pkg.packageName]
476 .getInitializedValue()
477 if (carrierPrivilegedStatus != CARRIER_PRIVILEGE_STATUS_HAS_ACCESS &&
478 carrierPrivilegedStatus != CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
479 DumpableLog.w(LOG_TAG, "Error carrier privileged status for ${pkg.packageName}: " +
480 carrierPrivilegedStatus)
481 }
482 if (carrierPrivilegedStatus == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
483 if (DEBUG) {
484 DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - carrier privileged")
485 }
486 return true
487 }
488 return false
489 }
490
491 /**
492 * Checks if the given package is exempt from auto revoke in a way that's user-overridable
493 */
isPackageAutoRevokeExemptnull494 suspend fun isPackageAutoRevokeExempt(
495 context: Context,
496 pkg: LightPackageInfo
497 ): Boolean {
498 val packageName = pkg.packageName
499 val packageUid = pkg.uid
500
501 val whitelistAppOpMode =
502 AppOpLiveData[packageName,
503 AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid]
504 .getInitializedValue()
505 if (whitelistAppOpMode == MODE_DEFAULT) {
506 // Initial state - whitelist not explicitly overridden by either user or installer
507
508 if (DEBUG_OVERRIDE_THRESHOLDS) {
509 // Suppress exemptions to allow debugging
510 return false
511 }
512
513 // Q- packages exempt by default, except for dogfooding
514 return pkg.targetSdkVersion <= android.os.Build.VERSION_CODES.Q &&
515 TeamfoodSettings.get(context)?.enabledForPreRApps != true
516 }
517 // Check whether user/installer exempt
518 return whitelistAppOpMode != MODE_ALLOWED
519 }
520
Contextnull521 private fun Context.isPackageCrossProfile(pkg: String): Boolean {
522 return packageManager.checkPermission(
523 Manifest.permission.INTERACT_ACROSS_PROFILES, pkg) == PERMISSION_GRANTED ||
524 packageManager.checkPermission(
525 Manifest.permission.INTERACT_ACROSS_USERS, pkg) == PERMISSION_GRANTED ||
526 packageManager.checkPermission(
527 Manifest.permission.INTERACT_ACROSS_USERS_FULL, pkg) == PERMISSION_GRANTED
528 }
529
Contextnull530 private fun Context.forUser(user: UserHandle): Context {
531 return Utils.getUserContext(application, user)
532 }
533
Contextnull534 private fun Context.forParentUser(): Context {
535 return Utils.getParentUserContext(this)
536 }
537
getSystemServicenull538 private inline fun <reified T> Context.getSystemService() = getSystemService(T::class.java)!!
539
540 val Context.sharedPreferences: SharedPreferences get() {
541 return PreferenceManager.getDefaultSharedPreferences(this)
542 }
543
544 private val Context.firstBootTime: Long get() {
545 var time = sharedPreferences.getLong(PREF_KEY_FIRST_BOOT_TIME, -1L)
546 if (time > 0) {
547 return time
548 }
549 // This is the first boot
550 time = System.currentTimeMillis()
551 sharedPreferences.edit().putLong(PREF_KEY_FIRST_BOOT_TIME, time).apply()
552 return time
553 }
554
555 /**
556 * A job to check for apps unused in the last [getUnusedThresholdMs]ms every
557 * [getCheckFrequencyMs]ms and [revokePermissionsOnUnusedApps] for them
558 */
559 class AutoRevokeService : JobService() {
560 var job: Job? = null
561 var jobStartTime: Long = -1L
562
onStartJobnull563 override fun onStartJob(params: JobParameters?): Boolean {
564 if (DEBUG) {
565 DumpableLog.i(LOG_TAG, "onStartJob")
566 }
567
568 if (SKIP_NEXT_RUN) {
569 SKIP_NEXT_RUN = false
570 if (DEBUG) {
571 Log.i(LOG_TAG, "Skipping auto revoke first run when scheduled by system")
572 }
573 jobFinished(params, false)
574 return true
575 }
576
577 jobStartTime = System.currentTimeMillis()
578 job = GlobalScope.launch(Main) {
579 try {
580 var sessionId = INVALID_SESSION_ID
581 while (sessionId == INVALID_SESSION_ID) {
582 sessionId = Random().nextLong()
583 }
584
585 val revokedApps = revokePermissionsOnUnusedApps(this@AutoRevokeService, sessionId)
586 if (revokedApps.isNotEmpty()) {
587 showAutoRevokeNotification(sessionId)
588 }
589 } catch (e: Exception) {
590 DumpableLog.e(LOG_TAG, "Failed to auto-revoke permissions", e)
591 }
592 jobFinished(params, false)
593 }
594 return true
595 }
596
showAutoRevokeNotificationnull597 private suspend fun showAutoRevokeNotification(sessionId: Long) {
598 val notificationManager = getSystemService(NotificationManager::class.java)!!
599
600 val permissionReminderChannel = NotificationChannel(
601 PERMISSION_REMINDER_CHANNEL_ID, getString(R.string.permission_reminders),
602 IMPORTANCE_LOW)
603 notificationManager.createNotificationChannel(permissionReminderChannel)
604
605 val clickIntent = Intent(this, ManagePermissionsActivity::class.java).apply {
606 action = ACTION_MANAGE_AUTO_REVOKE
607 putExtra(EXTRA_SESSION_ID, sessionId)
608 flags = Intent.FLAG_ACTIVITY_NEW_TASK
609 }
610 val pendingIntent = PendingIntent.getActivity(this, 0, clickIntent,
611 FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT)
612
613 val b = Notification.Builder(this, PERMISSION_REMINDER_CHANNEL_ID)
614 .setContentTitle(getString(R.string.auto_revoke_permission_notification_title))
615 .setContentText(getString(
616 R.string.auto_revoke_permission_notification_content))
617 .setStyle(Notification.BigTextStyle().bigText(getString(
618 R.string.auto_revoke_permission_notification_content)))
619 .setSmallIcon(R.drawable.ic_settings_24dp)
620 .setColor(getColor(android.R.color.system_notification_accent_color))
621 .setAutoCancel(true)
622 .setContentIntent(pendingIntent)
623 .extend(Notification.TvExtender())
624 Utils.getSettingsLabelForNotifications(applicationContext.packageManager)?.let {
625 settingsLabel ->
626 val extras = Bundle()
627 extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, settingsLabel.toString())
628 b.addExtras(extras)
629 }
630
631 notificationManager.notify(AutoRevokeService::class.java.simpleName,
632 AUTO_REVOKE_NOTIFICATION_ID, b.build())
633 // Preload the auto revoked packages
634 UnusedAutoRevokedPackagesLiveData.getInitializedValue()
635 }
636
637 companion object {
638 const val SHOW_AUTO_REVOKE = "showAutoRevoke"
639 }
640
onStopJobnull641 override fun onStopJob(params: JobParameters?): Boolean {
642 DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
643 job?.cancel()
644 return true
645 }
646 }
647
648 /**
649 * Packages using exempt services for the current user (package-name -> list<service-interfaces>
650 * implemented by the package)
651 */
652 class ExemptServicesLiveData(val user: UserHandle)
653 : SmartUpdateMediatorLiveData<Map<String, List<String>>>() {
654 private val serviceLiveDatas: List<SmartUpdateMediatorLiveData<Set<String>>> = listOf(
655 ServiceLiveData[InputMethod.SERVICE_INTERFACE,
656 Manifest.permission.BIND_INPUT_METHOD,
657 user],
658 ServiceLiveData[
659 NotificationListenerService.SERVICE_INTERFACE,
660 Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE,
661 user],
662 ServiceLiveData[
663 AccessibilityService.SERVICE_INTERFACE,
664 Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
665 user],
666 ServiceLiveData[
667 WallpaperService.SERVICE_INTERFACE,
668 Manifest.permission.BIND_WALLPAPER,
669 user],
670 ServiceLiveData[
671 VoiceInteractionService.SERVICE_INTERFACE,
672 Manifest.permission.BIND_VOICE_INTERACTION,
673 user],
674 ServiceLiveData[
675 AttentionService.SERVICE_INTERFACE,
676 Manifest.permission.BIND_ATTENTION_SERVICE,
677 user],
678 ServiceLiveData[
679 TextClassifierService.SERVICE_INTERFACE,
680 Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
681 user],
682 ServiceLiveData[
683 PrintService.SERVICE_INTERFACE,
684 Manifest.permission.BIND_PRINT_SERVICE,
685 user],
686 ServiceLiveData[
687 DreamService.SERVICE_INTERFACE,
688 Manifest.permission.BIND_DREAM_SERVICE,
689 user],
690 ServiceLiveData[
691 NetworkScoreManager.ACTION_RECOMMEND_NETWORKS,
692 Manifest.permission.BIND_NETWORK_RECOMMENDATION_SERVICE,
693 user],
694 ServiceLiveData[
695 AutofillService.SERVICE_INTERFACE,
696 Manifest.permission.BIND_AUTOFILL_SERVICE,
697 user],
698 ServiceLiveData[
699 AugmentedAutofillService.SERVICE_INTERFACE,
700 Manifest.permission.BIND_AUGMENTED_AUTOFILL_SERVICE,
701 user],
702 ServiceLiveData[
703 DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE,
704 Manifest.permission.BIND_DEVICE_ADMIN,
705 user],
706 BroadcastReceiverLiveData[
707 DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
708 Manifest.permission.BIND_DEVICE_ADMIN,
709 user]
710 )
711
712 init {
<lambda>null713 serviceLiveDatas.forEach { addSource(it) { updateIfActive() } }
714 }
715
onUpdatenull716 override fun onUpdate() {
717 if (serviceLiveDatas.all { it.isInitialized }) {
718 val pksToServices = mutableMapOf<String, MutableList<String>>()
719
720 serviceLiveDatas.forEach { serviceLD ->
721 serviceLD.value!!.forEach { packageName ->
722 pksToServices.getOrPut(packageName, { mutableListOf() })
723 .add((serviceLD as? HasIntentAction)?.intentAction ?: "???")
724 }
725 }
726
727 value = pksToServices
728 }
729 }
730
731 /**
732 * Repository for ExemptServiceLiveData
733 *
734 * <p> Key value is user
735 */
736 companion object : DataRepositoryForPackage<UserHandle, ExemptServicesLiveData>() {
newValuenull737 override fun newValue(key: UserHandle): ExemptServicesLiveData {
738 return ExemptServicesLiveData(key)
739 }
740 }
741 }
742
743 private data class TeamfoodSettings(
744 val enabledForPreRApps: Boolean,
745 val unusedThresholdMs: Long,
746 val checkFrequencyMs: Long
747 ) {
748 companion object {
749 private var cached: TeamfoodSettings? = null
750
getnull751 fun get(context: Context): TeamfoodSettings? {
752 if (cached != null) return cached
753
754 return Settings.Global.getString(context.contentResolver,
755 "auto_revoke_parameters" /* Settings.Global.AUTO_REVOKE_PARAMETERS */)?.let { str ->
756
757 if (DEBUG) {
758 DumpableLog.i(LOG_TAG, "Parsing teamfood setting value: $str")
759 }
760 str.split(",")
761 .mapNotNull {
762 val keyValue = it.split("=")
763 keyValue.getOrNull(0)?.let { key ->
764 key to keyValue.getOrNull(1)
765 }
766 }
767 .toMap()
768 .let { pairs ->
769 TeamfoodSettings(
770 enabledForPreRApps = pairs["enabledForPreRApps"] == "true",
771 unusedThresholdMs =
772 pairs["unusedThresholdMs"]?.toLongOrNull()
773 ?: DEFAULT_UNUSED_THRESHOLD_MS,
774 checkFrequencyMs = pairs["checkFrequencyMs"]?.toLongOrNull()
775 ?: DEFAULT_CHECK_FREQUENCY_MS)
776 }
777 }.also {
778 cached = it
779 if (DEBUG) {
780 Log.i(LOG_TAG, "Parsed teamfood setting value: $it")
781 }
782 }
783 }
784 }
785
786 /**
787 * @return team food settings for dumping as as a proto
788 */
dumpnull789 suspend fun dump(): TeamFoodSettingsProto {
790 return TeamFoodSettingsProto.newBuilder()
791 .setEnabledForPreRApps(enabledForPreRApps)
792 .setUnusedThresholdMillis(unusedThresholdMs)
793 .setCheckFrequencyMillis(checkFrequencyMs)
794 .build()
795 }
796 }
797
798 /** Data interesting to auto-revoke */
799 private class AutoRevokeDumpLiveData(context: Context) :
800 SmartUpdateMediatorLiveData<AutoRevokeDumpLiveData.AutoRevokeDumpData>() {
801 /** All data */
802 data class AutoRevokeDumpData(
803 val users: List<AutoRevokeDumpUserData>
804 ) {
dumpUsersnull805 fun dumpUsers(): List<PerUserProto> {
806 return users.map { it.dump() }
807 }
808 }
809
810 /** Per user data */
811 data class AutoRevokeDumpUserData(
812 val user: UserHandle,
813 val pkgs: List<AutoRevokeDumpPackageData>
814 ) {
dumpnull815 fun dump(): PerUserProto {
816 val dump = PerUserProto.newBuilder()
817 .setUserId(user.identifier)
818
819 pkgs.forEach { dump.addPackages(it.dump()) }
820
821 return dump.build()
822 }
823 }
824
825 /** Per package data */
826 data class AutoRevokeDumpPackageData(
827 val uid: Int,
828 val packageName: String,
829 val firstInstallTime: Long,
830 val lastTimeVisible: Long?,
831 val implementedServices: List<String>,
832 val groups: List<AutoRevokeDumpGroupData>
833 ) {
dumpnull834 fun dump(): PackageProto {
835 val dump = PackageProto.newBuilder()
836 .setUid(uid)
837 .setPackageName(packageName)
838 .setFirstInstallTime(firstInstallTime)
839
840 lastTimeVisible?.let { dump.lastTimeVisible = lastTimeVisible }
841
842 implementedServices.forEach { dump.addImplementedServices(it) }
843
844 groups.forEach { dump.addGroups(it.dump()) }
845
846 return dump.build()
847 }
848 }
849
850 /** Per permission group data */
851 data class AutoRevokeDumpGroupData(
852 val groupName: String,
853 val isFixed: Boolean,
854 val isAnyGrantedIncludingAppOp: Boolean,
855 val isGrantedByDefault: Boolean,
856 val isGrantedByRole: Boolean,
857 val isUserSensitive: Boolean,
858 val isAutoRevoked: Boolean
859 ) {
dumpnull860 fun dump(): PermissionGroupProto {
861 return PermissionGroupProto.newBuilder()
862 .setGroupName(groupName)
863 .setIsFixed(isFixed)
864 .setIsAnyGrantedIncludingAppop(isAnyGrantedIncludingAppOp)
865 .setIsGrantedByDefault(isGrantedByDefault)
866 .setIsGrantedByRole(isGrantedByRole)
867 .setIsUserSensitive(isUserSensitive)
868 .setIsAutoRevoked(isAutoRevoked)
869 .build()
870 }
871 }
872
873 /** All users */
874 private val users = UsersLiveData
875
876 /** Exempt services for each user: user -> services */
877 private var services: MutableMap<UserHandle, ExemptServicesLiveData>? = null
878
879 /** Usage stats: user -> list<usages> */
880 private val usages = UsageStatsLiveData[
881 getUnusedThresholdMs(context),
882 if (DEBUG_OVERRIDE_THRESHOLDS) INTERVAL_DAILY else INTERVAL_MONTHLY
883 ]
884
885 /** All package infos: user -> pkg **/
886 private val packages = AllPackageInfosLiveData
887
888 /** Group names of revoked permission groups: (user, pkg-name) -> set<group-name> **/
889 private val revokedPermGroupNames = UnusedAutoRevokedPackagesLiveData
890
891 /**
892 * Group names for packages
893 * map<user, pkg-name> -> list<perm-group-name>. {@code null} before step 1
894 */
895 private var pkgPermGroupNames:
896 MutableMap<Pair<UserHandle, String>, PackagePermissionsLiveData>? = null
897
898 /**
899 * Group state for packages
900 * map<(user, pkg-name) -> map<perm-group-name -> group>>, value {@code null} before step 2
901 */
902 private val pkgPermGroups =
903 mutableMapOf<Pair<UserHandle, String>,
904 MutableMap<String, LightAppPermGroupLiveData>?>()
905
906 /** If this live-data currently inside onUpdate */
907 private var isUpdating = false
908
<lambda>null909 init {
910 addSource(revokedPermGroupNames) {
911 updateIfActive()
912 }
913
914 addSource(users) {
915 services?.values?.forEach { removeSource(it) }
916 services = null
917
918 updateIfActive()
919 }
920
921 addSource(usages) {
922 updateIfActive()
923 }
924
925 addSource(packages) {
926 pkgPermGroupNames?.values?.forEach { removeSource(it) }
927 pkgPermGroupNames = null
928 pkgPermGroups.values.forEach { it?.values?.forEach { removeSource(it) } }
929
930 updateIfActive()
931 }
932 }
933
onUpdatenull934 override fun onUpdate() {
935 // If a source is already ready, the call onUpdate when added. Suppress this
936 if (isUpdating) {
937 return
938 }
939 isUpdating = true
940
941 // services/autoRevokeManifestExemptPackages step 1, users is loaded, nothing else
942 if (users.isInitialized && services == null) {
943 services = mutableMapOf()
944
945 for (user in users.value!!) {
946 val newServices = ExemptServicesLiveData[user]
947 services!![user] = newServices
948
949 addSource(newServices) {
950 updateIfActive()
951 }
952 }
953 }
954
955 // pkgPermGroupNames step 1, packages is loaded, nothing else
956 if (packages.isInitialized && pkgPermGroupNames == null) {
957 pkgPermGroupNames = mutableMapOf()
958
959 for ((user, userPkgs) in packages.value!!) {
960 for (pkg in userPkgs) {
961 val newPermGroupNames = PackagePermissionsLiveData[pkg.packageName, user]
962 pkgPermGroupNames!![user to pkg.packageName] = newPermGroupNames
963
964 addSource(newPermGroupNames) {
965 pkgPermGroups[user to pkg.packageName]?.forEach { removeSource(it.value) }
966 pkgPermGroups.remove(user to pkg.packageName)
967
968 updateIfActive()
969 }
970 }
971 }
972 }
973
974 // pkgPermGroupNames step 2, packages and pkgPermGroupNames are loaded, but pkgPermGroups
975 // are not loaded yet
976 if (packages.isInitialized && pkgPermGroupNames != null) {
977 for ((user, userPkgs) in packages.value!!) {
978 for (pkg in userPkgs) {
979 if (pkgPermGroupNames!![user to pkg.packageName]?.isInitialized == true &&
980 pkgPermGroups[user to pkg.packageName] == null) {
981 pkgPermGroups[user to pkg.packageName] = mutableMapOf()
982
983 for (groupName in
984 pkgPermGroupNames!![user to pkg.packageName]!!.value!!.keys) {
985 if (groupName == PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS) {
986 continue
987 }
988
989 val newPkgPermGroup = LightAppPermGroupLiveData[pkg.packageName,
990 groupName, user]
991
992 pkgPermGroups[user to pkg.packageName]!![groupName] = newPkgPermGroup
993
994 addSource(newPkgPermGroup) { updateIfActive() }
995 }
996 }
997 }
998 }
999 }
1000
1001 // Final step, everything is loaded, generate data
1002 if (packages.isInitialized && usages.isInitialized && revokedPermGroupNames.isInitialized &&
1003 pkgPermGroupNames?.values?.all { it.isInitialized } == true &&
1004 pkgPermGroupNames?.size == pkgPermGroups.size &&
1005 pkgPermGroups.values.all { it?.values?.all { it.isInitialized } == true } &&
1006 services?.values?.all { it.isInitialized } == true) {
1007 val users = mutableListOf<AutoRevokeDumpUserData>()
1008
1009 for ((user, userPkgs) in packages.value!!) {
1010 val pkgs = mutableListOf<AutoRevokeDumpPackageData>()
1011
1012 for (pkg in userPkgs) {
1013 val groups = mutableListOf<AutoRevokeDumpGroupData>()
1014
1015 for (groupName in pkgPermGroupNames!![user to pkg.packageName]!!.value!!.keys) {
1016 if (groupName == PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS) {
1017 continue
1018 }
1019
1020 pkgPermGroups[user to pkg.packageName]?.let {
1021 it[groupName]?.value?.apply {
1022 groups.add(AutoRevokeDumpGroupData(groupName,
1023 isBackgroundFixed || isForegroundFixed,
1024 permissions.any { (_, p) -> p.isGrantedIncludingAppOp },
1025 isGrantedByDefault,
1026 isGrantedByRole,
1027 isUserSensitive,
1028 revokedPermGroupNames.value?.let {
1029 it[pkg.packageName to user]
1030 ?.contains(groupName)
1031 } == true
1032 ))
1033 }
1034 }
1035 }
1036
1037 pkgs.add(AutoRevokeDumpPackageData(pkg.uid, pkg.packageName,
1038 pkg.firstInstallTime,
1039 usages.value!![user]?.lastTimeVisible(pkg.packageName),
1040 services!![user]?.value!![pkg.packageName] ?: emptyList(),
1041 groups))
1042 }
1043
1044 users.add(AutoRevokeDumpUserData(user, pkgs))
1045 }
1046
1047 value = AutoRevokeDumpData(users)
1048 }
1049
1050 isUpdating = false
1051 }
1052 }
1053