1 /* <lambda>null2 * Copyright (C) 2022 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.systemui.qs 18 19 import android.app.IActivityManager 20 import android.app.IForegroundServiceObserver 21 import android.app.job.IUserVisibleJobObserver 22 import android.app.job.JobScheduler 23 import android.app.job.UserVisibleJobSummary 24 import android.content.BroadcastReceiver 25 import android.content.Context 26 import android.content.Intent 27 import android.content.IntentFilter 28 import android.content.pm.PackageManager 29 import android.content.pm.UserInfo 30 import android.graphics.drawable.Drawable 31 import android.os.IBinder 32 import android.os.PowerExemptionManager 33 import android.os.RemoteException 34 import android.os.UserHandle 35 import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI 36 import android.text.format.DateUtils 37 import android.util.ArrayMap 38 import android.util.IndentingPrintWriter 39 import android.view.LayoutInflater 40 import android.view.View 41 import android.view.ViewGroup 42 import android.widget.Button 43 import android.widget.ImageView 44 import android.widget.TextView 45 import androidx.annotation.GuardedBy 46 import androidx.annotation.VisibleForTesting 47 import androidx.recyclerview.widget.DiffUtil 48 import androidx.recyclerview.widget.LinearLayoutManager 49 import androidx.recyclerview.widget.RecyclerView 50 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED 51 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT 52 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS 53 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS 54 import com.android.internal.jank.InteractionJankMonitor 55 import com.android.systemui.Dumpable 56 import com.android.systemui.R 57 import com.android.systemui.animation.DialogCuj 58 import com.android.systemui.animation.DialogLaunchAnimator 59 import com.android.systemui.animation.Expandable 60 import com.android.systemui.broadcast.BroadcastDispatcher 61 import com.android.systemui.dagger.SysUISingleton 62 import com.android.systemui.dagger.qualifiers.Background 63 import com.android.systemui.dagger.qualifiers.Main 64 import com.android.systemui.dump.DumpManager 65 import com.android.systemui.settings.UserTracker 66 import com.android.systemui.shared.system.SysUiStatsLog 67 import com.android.systemui.statusbar.phone.SystemUIDialog 68 import com.android.systemui.util.DeviceConfigProxy 69 import com.android.systemui.util.indentIfPossible 70 import com.android.systemui.util.time.SystemClock 71 import java.io.PrintWriter 72 import java.util.Objects 73 import java.util.concurrent.Executor 74 import javax.inject.Inject 75 import kotlin.math.max 76 import kotlinx.coroutines.flow.MutableStateFlow 77 import kotlinx.coroutines.flow.StateFlow 78 import kotlinx.coroutines.flow.asStateFlow 79 80 /** A controller for the dealing with services running in the foreground. */ 81 interface FgsManagerController { 82 /** Whether the TaskManager (and therefore this controller) is actually available. */ 83 val isAvailable: StateFlow<Boolean> 84 85 /** The number of packages with a service running in the foreground. */ 86 val numRunningPackages: Int 87 88 /** 89 * Whether there were new changes to the foreground services since the last [shown][showDialog] 90 * dialog was dismissed. 91 */ 92 val newChangesSinceDialogWasDismissed: Boolean 93 94 /** 95 * Whether we should show a dot to indicate when [newChangesSinceDialogWasDismissed] is true. 96 */ 97 val showFooterDot: StateFlow<Boolean> 98 99 val includesUserVisibleJobs: Boolean 100 101 /** 102 * Initialize this controller. This should be called once, before this controller is used for 103 * the first time. 104 */ 105 fun init() 106 107 /** 108 * Show the foreground services dialog. The dialog will be expanded from [expandable] if 109 * it's not `null`. 110 */ 111 fun showDialog(expandable: Expandable?) 112 113 /** Add a [OnNumberOfPackagesChangedListener]. */ 114 fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) 115 116 /** Remove a [OnNumberOfPackagesChangedListener]. */ 117 fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) 118 119 /** Add a [OnDialogDismissedListener]. */ 120 fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) 121 122 /** Remove a [OnDialogDismissedListener]. */ 123 fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) 124 125 @VisibleForTesting 126 fun visibleButtonsCount(): Int 127 128 interface OnNumberOfPackagesChangedListener { 129 /** Called when [numRunningPackages] changed. */ 130 fun onNumberOfPackagesChanged(numPackages: Int) 131 } 132 133 interface OnDialogDismissedListener { 134 /** Called when a dialog shown using [showDialog] was dismissed. */ 135 fun onDialogDismissed() 136 } 137 } 138 139 @SysUISingleton 140 class FgsManagerControllerImpl @Inject constructor( 141 private val context: Context, 142 @Main private val mainExecutor: Executor, 143 @Background private val backgroundExecutor: Executor, 144 private val systemClock: SystemClock, 145 private val activityManager: IActivityManager, 146 private val jobScheduler: JobScheduler, 147 private val packageManager: PackageManager, 148 private val userTracker: UserTracker, 149 private val deviceConfigProxy: DeviceConfigProxy, 150 private val dialogLaunchAnimator: DialogLaunchAnimator, 151 private val broadcastDispatcher: BroadcastDispatcher, 152 private val dumpManager: DumpManager 153 ) : Dumpable, FgsManagerController { 154 155 companion object { 156 private const val INTERACTION_JANK_TAG = "active_background_apps" 157 private const val DEFAULT_TASK_MANAGER_ENABLED = true 158 private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false 159 private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true 160 private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = false 161 } 162 163 override var newChangesSinceDialogWasDismissed = false 164 private set 165 166 val _isAvailable = MutableStateFlow(false) 167 override val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow() 168 169 val _showFooterDot = MutableStateFlow(false) 170 override val showFooterDot: StateFlow<Boolean> = _showFooterDot.asStateFlow() 171 172 private var showStopBtnForUserAllowlistedApps = false 173 174 private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS 175 176 override val includesUserVisibleJobs: Boolean 177 get() = showUserVisibleJobs 178 179 override val numRunningPackages: Int 180 get() { <lambda>null181 synchronized(lock) { 182 return getNumVisiblePackagesLocked() 183 } 184 } 185 186 private val lock = Any() 187 188 @GuardedBy("lock") 189 var initialized = false 190 191 @GuardedBy("lock") 192 private var lastNumberOfVisiblePackages = 0 193 194 @GuardedBy("lock") 195 private var currentProfileIds = mutableSetOf<Int>() 196 197 @GuardedBy("lock") 198 private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>() 199 200 @GuardedBy("lock") 201 private var dialog: SystemUIDialog? = null 202 203 @GuardedBy("lock") 204 private val appListAdapter: AppListAdapter = AppListAdapter() 205 206 @GuardedBy("lock") 207 private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap() 208 209 private val userTrackerCallback = object : UserTracker.Callback { onUserChangednull210 override fun onUserChanged(newUser: Int, userContext: Context) {} 211 onProfilesChangednull212 override fun onProfilesChanged(profiles: List<UserInfo>) { 213 synchronized(lock) { 214 currentProfileIds.clear() 215 currentProfileIds.addAll(profiles.map { it.id }) 216 lastNumberOfVisiblePackages = 0 217 updateNumberOfVisibleRunningPackagesLocked() 218 } 219 } 220 } 221 222 private val foregroundServiceObserver = ForegroundServiceObserver() 223 224 private val userVisibleJobObserver = UserVisibleJobObserver() 225 initnull226 override fun init() { 227 synchronized(lock) { 228 if (initialized) { 229 return 230 } 231 232 showUserVisibleJobs = deviceConfigProxy.getBoolean( 233 NAMESPACE_SYSTEMUI, 234 TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS) 235 236 try { 237 activityManager.registerForegroundServiceObserver(foregroundServiceObserver) 238 // Clumping FGS and user-visible jobs here and showing a single entry and button 239 // for them is the easiest way to get user-visible jobs showing in Task Manager. 240 // Ideally, we would have dedicated UI in task manager for the user-visible jobs. 241 // TODO(255768978): distinguish jobs from FGS and give users more control 242 if (showUserVisibleJobs) { 243 jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver) 244 } 245 } catch (e: RemoteException) { 246 e.rethrowFromSystemServer() 247 } 248 249 userTracker.addCallback(userTrackerCallback, backgroundExecutor) 250 251 currentProfileIds.addAll(userTracker.userProfiles.map { it.id }) 252 253 deviceConfigProxy.addOnPropertiesChangedListener( 254 NAMESPACE_SYSTEMUI, 255 backgroundExecutor 256 ) { 257 _isAvailable.value = it.getBoolean(TASK_MANAGER_ENABLED, _isAvailable.value) 258 _showFooterDot.value = 259 it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value) 260 showStopBtnForUserAllowlistedApps = it.getBoolean( 261 TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, 262 showStopBtnForUserAllowlistedApps) 263 var wasShowingUserVisibleJobs = showUserVisibleJobs 264 showUserVisibleJobs = it.getBoolean( 265 TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs) 266 if (showUserVisibleJobs != wasShowingUserVisibleJobs) { 267 onShowUserVisibleJobsFlagChanged() 268 } 269 } 270 271 _isAvailable.value = deviceConfigProxy.getBoolean( 272 NAMESPACE_SYSTEMUI, 273 TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED 274 ) 275 _showFooterDot.value = deviceConfigProxy.getBoolean( 276 NAMESPACE_SYSTEMUI, 277 TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT 278 ) 279 showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean( 280 NAMESPACE_SYSTEMUI, 281 TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, 282 DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS) 283 284 dumpManager.registerDumpable(this) 285 286 broadcastDispatcher.registerReceiver( 287 object : BroadcastReceiver() { 288 override fun onReceive(context: Context, intent: Intent) { 289 if (intent.action == Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER) { 290 showDialog(null) 291 } 292 } 293 }, 294 IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER), 295 executor = mainExecutor, 296 flags = Context.RECEIVER_NOT_EXPORTED 297 ) 298 299 initialized = true 300 } 301 } 302 303 @GuardedBy("lock") 304 private val onNumberOfPackagesChangedListeners = 305 mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>() 306 307 @GuardedBy("lock") 308 private val onDialogDismissedListeners = 309 mutableSetOf<FgsManagerController.OnDialogDismissedListener>() 310 addOnNumberOfPackagesChangedListenernull311 override fun addOnNumberOfPackagesChangedListener( 312 listener: FgsManagerController.OnNumberOfPackagesChangedListener 313 ) { 314 synchronized(lock) { 315 onNumberOfPackagesChangedListeners.add(listener) 316 } 317 } 318 removeOnNumberOfPackagesChangedListenernull319 override fun removeOnNumberOfPackagesChangedListener( 320 listener: FgsManagerController.OnNumberOfPackagesChangedListener 321 ) { 322 synchronized(lock) { 323 onNumberOfPackagesChangedListeners.remove(listener) 324 } 325 } 326 addOnDialogDismissedListenernull327 override fun addOnDialogDismissedListener( 328 listener: FgsManagerController.OnDialogDismissedListener 329 ) { 330 synchronized(lock) { 331 onDialogDismissedListeners.add(listener) 332 } 333 } 334 removeOnDialogDismissedListenernull335 override fun removeOnDialogDismissedListener( 336 listener: FgsManagerController.OnDialogDismissedListener 337 ) { 338 synchronized(lock) { 339 onDialogDismissedListeners.remove(listener) 340 } 341 } 342 getNumVisiblePackagesLockednull343 private fun getNumVisiblePackagesLocked(): Int { 344 return runningTaskIdentifiers.keys.count { 345 it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId) 346 } 347 } 348 updateNumberOfVisibleRunningPackagesLockednull349 private fun updateNumberOfVisibleRunningPackagesLocked() { 350 val num = getNumVisiblePackagesLocked() 351 if (num != lastNumberOfVisiblePackages) { 352 lastNumberOfVisiblePackages = num 353 newChangesSinceDialogWasDismissed = true 354 onNumberOfPackagesChangedListeners.forEach { 355 backgroundExecutor.execute { 356 it.onNumberOfPackagesChanged(num) 357 } 358 } 359 } 360 } 361 visibleButtonsCountnull362 override fun visibleButtonsCount(): Int { 363 synchronized(lock) { 364 return getNumVisibleButtonsLocked() 365 } 366 } 367 getNumVisibleButtonsLockednull368 private fun getNumVisibleButtonsLocked(): Int { 369 return runningTaskIdentifiers.keys.count { 370 it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId) 371 } 372 } 373 showDialognull374 override fun showDialog(expandable: Expandable?) { 375 synchronized(lock) { 376 if (dialog == null) { 377 378 runningTaskIdentifiers.keys.forEach { 379 it.updateUiControl() 380 } 381 382 val dialog = SystemUIDialog(context) 383 dialog.setTitle(R.string.fgs_manager_dialog_title) 384 dialog.setMessage(R.string.fgs_manager_dialog_message) 385 386 val dialogContext = dialog.context 387 388 val recyclerView = RecyclerView(dialogContext) 389 recyclerView.layoutManager = LinearLayoutManager(dialogContext) 390 recyclerView.adapter = appListAdapter 391 392 val topSpacing = dialogContext.resources 393 .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing) 394 dialog.setView(recyclerView, 0, topSpacing, 0, 0) 395 396 this.dialog = dialog 397 398 dialog.setOnDismissListener { 399 newChangesSinceDialogWasDismissed = false 400 synchronized(lock) { 401 this.dialog = null 402 updateAppItemsLocked() 403 } 404 onDialogDismissedListeners.forEach { 405 mainExecutor.execute(it::onDialogDismissed) 406 } 407 } 408 409 mainExecutor.execute { 410 val controller = 411 expandable?.dialogLaunchController( 412 DialogCuj( 413 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, 414 INTERACTION_JANK_TAG, 415 ) 416 ) 417 if (controller != null) { 418 dialogLaunchAnimator.show(dialog, controller) 419 } else { 420 dialog.show() 421 } 422 } 423 424 backgroundExecutor.execute { 425 synchronized(lock) { 426 updateAppItemsLocked() 427 } 428 } 429 } 430 } 431 } 432 433 @GuardedBy("lock") updateAppItemsLockednull434 private fun updateAppItemsLocked() { 435 if (dialog == null) { 436 runningApps.clear() 437 return 438 } 439 440 val addedPackages = runningTaskIdentifiers.keys.filter { 441 currentProfileIds.contains(it.userId) && 442 it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true 443 } 444 val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) } 445 446 addedPackages.forEach { 447 val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) 448 runningApps[it] = RunningApp( 449 it.userId, it.packageName, 450 runningTaskIdentifiers[it]!!.startTime, it.uiControl, 451 packageManager.getApplicationLabel(ai), 452 packageManager.getUserBadgedIcon( 453 packageManager.getApplicationIcon(ai), UserHandle.of(it.userId) 454 ) 455 ) 456 logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted) 457 } 458 459 removedPackages.forEach { pkg -> 460 val ra = runningApps[pkg]!! 461 val ra2 = ra.copy().also { 462 it.stopped = true 463 it.appLabel = ra.appLabel 464 it.icon = ra.icon 465 } 466 runningApps[pkg] = ra2 467 } 468 469 mainExecutor.execute { 470 appListAdapter 471 .setData(runningApps.values.toList().sortedByDescending { it.timeStarted }) 472 } 473 } 474 stopPackagenull475 private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) { 476 logEvent(stopped = true, packageName, userId, timeStarted) 477 val userPackageKey = UserPackage(userId, packageName) 478 if (showUserVisibleJobs && 479 runningTaskIdentifiers[userPackageKey]?.hasRunningJobs() == true) { 480 // TODO(255768978): allow fine-grained job control 481 jobScheduler.stopUserVisibleJobsForUser(packageName, userId) 482 } 483 if (runningTaskIdentifiers[userPackageKey]?.hasFgs() == true) { 484 activityManager.stopAppForUser(packageName, userId) 485 } 486 } 487 onShowUserVisibleJobsFlagChangednull488 private fun onShowUserVisibleJobsFlagChanged() { 489 if (showUserVisibleJobs) { 490 jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver) 491 } else { 492 jobScheduler.unregisterUserVisibleJobObserver(userVisibleJobObserver) 493 494 synchronized(lock) { 495 for ((userPackage, startTimeAndIdentifiers) in runningTaskIdentifiers) { 496 if (startTimeAndIdentifiers.hasFgs()) { 497 // The app still has FGS running, so all we need to do is remove 498 // the job summaries 499 startTimeAndIdentifiers.clearJobSummaries() 500 } else { 501 // The app only has user-visible jobs running, so remove it from 502 // the map altogether 503 runningTaskIdentifiers.remove(userPackage) 504 } 505 } 506 507 updateNumberOfVisibleRunningPackagesLocked() 508 509 updateAppItemsLocked() 510 } 511 } 512 } 513 logEventnull514 private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) { 515 val timeLogged = systemClock.elapsedRealtime() 516 val event = if (stopped) { 517 SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED 518 } else { 519 SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED 520 } 521 backgroundExecutor.execute { 522 val uid = packageManager.getPackageUidAsUser(packageName, userId) 523 SysUiStatsLog.write( 524 SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event, 525 timeLogged - timeStarted 526 ) 527 } 528 } 529 530 private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() { 531 private val lock = Any() 532 533 @GuardedBy("lock") 534 private var data: List<RunningApp> = listOf() 535 onCreateViewHoldernull536 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder { 537 return AppItemViewHolder( 538 LayoutInflater.from(parent.context) 539 .inflate(R.layout.fgs_manager_app_item, parent, false) 540 ) 541 } 542 onBindViewHoldernull543 override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) { 544 var runningApp: RunningApp 545 synchronized(lock) { 546 runningApp = data[position] 547 } 548 with(holder) { 549 iconView.setImageDrawable(runningApp.icon) 550 appLabelView.text = runningApp.appLabel 551 durationView.text = DateUtils.formatDuration( 552 max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000), 553 DateUtils.LENGTH_MEDIUM 554 ) 555 stopButton.setOnClickListener { 556 stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label) 557 stopPackage(runningApp.userId, runningApp.packageName, runningApp.timeStarted) 558 } 559 if (runningApp.uiControl == UIControl.HIDE_BUTTON) { 560 stopButton.visibility = View.INVISIBLE 561 } 562 if (runningApp.stopped) { 563 stopButton.isEnabled = false 564 stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label) 565 durationView.visibility = View.GONE 566 } else { 567 stopButton.isEnabled = true 568 stopButton.setText(R.string.fgs_manager_app_item_stop_button_label) 569 durationView.visibility = View.VISIBLE 570 } 571 } 572 } 573 getItemCountnull574 override fun getItemCount(): Int { 575 return data.size 576 } 577 setDatanull578 fun setData(newData: List<RunningApp>) { 579 var oldData = data 580 data = newData 581 582 DiffUtil.calculateDiff(object : DiffUtil.Callback() { 583 override fun getOldListSize(): Int { 584 return oldData.size 585 } 586 587 override fun getNewListSize(): Int { 588 return newData.size 589 } 590 591 override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): 592 Boolean { 593 return oldData[oldItemPosition] == newData[newItemPosition] 594 } 595 596 override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): 597 Boolean { 598 return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped 599 } 600 }).dispatchUpdatesTo(this) 601 } 602 } 603 604 private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() { onForegroundStateChangednull605 override fun onForegroundStateChanged( 606 token: IBinder, 607 packageName: String, 608 userId: Int, 609 isForeground: Boolean 610 ) { 611 synchronized(lock) { 612 val userPackageKey = UserPackage(userId, packageName) 613 if (isForeground) { 614 runningTaskIdentifiers 615 .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) } 616 .addFgsToken(token) 617 } else { 618 if (runningTaskIdentifiers[userPackageKey]?.also { 619 it.removeFgsToken(token) 620 }?.isEmpty() == true 621 ) { 622 runningTaskIdentifiers.remove(userPackageKey) 623 } 624 } 625 626 updateNumberOfVisibleRunningPackagesLocked() 627 628 updateAppItemsLocked() 629 } 630 } 631 } 632 633 private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() { onUserVisibleJobStateChangednull634 override fun onUserVisibleJobStateChanged( 635 summary: UserVisibleJobSummary, 636 isRunning: Boolean 637 ) { 638 synchronized(lock) { 639 val userPackageKey = UserPackage(summary.sourceUserId, summary.sourcePackageName) 640 if (isRunning) { 641 runningTaskIdentifiers 642 .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) } 643 .addJobSummary(summary) 644 } else { 645 if (runningTaskIdentifiers[userPackageKey]?.also { 646 it.removeJobSummary(summary) 647 }?.isEmpty() == true 648 ) { 649 runningTaskIdentifiers.remove(userPackageKey) 650 } 651 } 652 653 updateNumberOfVisibleRunningPackagesLocked() 654 655 updateAppItemsLocked() 656 } 657 } 658 } 659 660 private inner class UserPackage( 661 val userId: Int, 662 val packageName: String 663 ) { <lambda>null664 val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) } 665 var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED 666 667 private var uiControlInitialized = false 668 var uiControl: UIControl = UIControl.NORMAL 669 get() { 670 if (!uiControlInitialized) { 671 updateUiControl() 672 } 673 return field 674 } 675 private set 676 updateUiControlnull677 fun updateUiControl() { 678 backgroundRestrictionExemptionReason = 679 activityManager.getBackgroundRestrictionExemptionReason(uid) 680 uiControl = when (backgroundRestrictionExemptionReason) { 681 PowerExemptionManager.REASON_SYSTEM_UID, 682 PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY 683 684 PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED, 685 PowerExemptionManager.REASON_DEVICE_OWNER, 686 PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL, 687 PowerExemptionManager.REASON_DPO_PROTECTED_APP, 688 PowerExemptionManager.REASON_PROFILE_OWNER, 689 PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN, 690 PowerExemptionManager.REASON_PROC_STATE_PERSISTENT, 691 PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI, 692 PowerExemptionManager.REASON_ROLE_DIALER, 693 PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON 694 695 PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE -> 696 if (showStopBtnForUserAllowlistedApps) { 697 UIControl.NORMAL 698 } else { 699 UIControl.HIDE_BUTTON 700 } 701 else -> UIControl.NORMAL 702 } 703 uiControlInitialized = true 704 } 705 equalsnull706 override fun equals(other: Any?): Boolean { 707 if (other !is UserPackage) { 708 return false 709 } 710 return other.packageName == packageName && other.userId == userId 711 } 712 hashCodenull713 override fun hashCode(): Int = Objects.hash(userId, packageName) 714 715 fun dump(pw: PrintWriter) { 716 pw.println("UserPackage: [") 717 pw.indentIfPossible { 718 pw.println("userId=$userId") 719 pw.println("packageName=$packageName") 720 pw.println("uiControl=$uiControl (reason=$backgroundRestrictionExemptionReason)") 721 } 722 pw.println("]") 723 } 724 } 725 726 private data class StartTimeAndIdentifiers( 727 val systemClock: SystemClock 728 ) { 729 val startTime = systemClock.elapsedRealtime() 730 val fgsTokens = mutableSetOf<IBinder>() 731 val jobSummaries = mutableSetOf<UserVisibleJobSummary>() 732 addJobSummarynull733 fun addJobSummary(summary: UserVisibleJobSummary) { 734 jobSummaries.add(summary) 735 } 736 clearJobSummariesnull737 fun clearJobSummaries() { 738 jobSummaries.clear() 739 } 740 removeJobSummarynull741 fun removeJobSummary(summary: UserVisibleJobSummary) { 742 jobSummaries.remove(summary) 743 } 744 addFgsTokennull745 fun addFgsToken(token: IBinder) { 746 fgsTokens.add(token) 747 } 748 removeFgsTokennull749 fun removeFgsToken(token: IBinder) { 750 fgsTokens.remove(token) 751 } 752 hasFgsnull753 fun hasFgs(): Boolean { 754 return !fgsTokens.isEmpty() 755 } 756 hasRunningJobsnull757 fun hasRunningJobs(): Boolean { 758 return !jobSummaries.isEmpty() 759 } 760 isEmptynull761 fun isEmpty(): Boolean { 762 return fgsTokens.isEmpty() && jobSummaries.isEmpty() 763 } 764 dumpnull765 fun dump(pw: PrintWriter) { 766 pw.println("StartTimeAndIdentifiers: [") 767 pw.indentIfPossible { 768 pw.println( 769 "startTime=$startTime (time running =" + 770 " ${systemClock.elapsedRealtime() - startTime}ms)" 771 ) 772 pw.println("fgs tokens: [") 773 pw.indentIfPossible { 774 for (token in fgsTokens) { 775 pw.println("$token") 776 } 777 } 778 pw.println("job summaries: [") 779 pw.indentIfPossible { 780 for (summary in jobSummaries) { 781 pw.println("$summary") 782 } 783 } 784 pw.println("]") 785 } 786 pw.println("]") 787 } 788 } 789 790 private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) { 791 val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label) 792 val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration) 793 val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon) 794 val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button) 795 } 796 797 private data class RunningApp( 798 val userId: Int, 799 val packageName: String, 800 val timeStarted: Long, 801 val uiControl: UIControl 802 ) { 803 constructor( 804 userId: Int, 805 packageName: String, 806 timeStarted: Long, 807 uiControl: UIControl, 808 appLabel: CharSequence, 809 icon: Drawable 810 ) : this(userId, packageName, timeStarted, uiControl) { 811 this.appLabel = appLabel 812 this.icon = icon 813 } 814 815 // variables to keep out of the generated equals() 816 var appLabel: CharSequence = "" 817 var icon: Drawable? = null 818 var stopped = false 819 dumpnull820 fun dump(pw: PrintWriter, systemClock: SystemClock) { 821 pw.println("RunningApp: [") 822 pw.indentIfPossible { 823 pw.println("userId=$userId") 824 pw.println("packageName=$packageName") 825 pw.println( 826 "timeStarted=$timeStarted (time since start =" + 827 " ${systemClock.elapsedRealtime() - timeStarted}ms)" 828 ) 829 pw.println("uiControl=$uiControl") 830 pw.println("appLabel=$appLabel") 831 pw.println("icon=$icon") 832 pw.println("stopped=$stopped") 833 } 834 pw.println("]") 835 } 836 } 837 838 private enum class UIControl { 839 NORMAL, HIDE_BUTTON, HIDE_ENTRY 840 } 841 dumpnull842 override fun dump(printwriter: PrintWriter, args: Array<out String>) { 843 val pw = IndentingPrintWriter(printwriter) 844 synchronized(lock) { 845 pw.println("current user profiles = $currentProfileIds") 846 pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed") 847 pw.println("Running task identifiers: [") 848 pw.indentIfPossible { 849 runningTaskIdentifiers.forEach { (userPackage, startTimeAndIdentifiers) -> 850 pw.println("{") 851 pw.indentIfPossible { 852 userPackage.dump(pw) 853 startTimeAndIdentifiers.dump(pw) 854 } 855 pw.println("}") 856 } 857 } 858 pw.println("]") 859 860 pw.println("Loaded package UI info: [") 861 pw.indentIfPossible { 862 runningApps.forEach { (userPackage, runningApp) -> 863 pw.println("{") 864 pw.indentIfPossible { 865 userPackage.dump(pw) 866 runningApp.dump(pw, systemClock) 867 } 868 pw.println("}") 869 } 870 } 871 pw.println("]") 872 } 873 } 874 } 875