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.annotation.WorkerThread 48 import androidx.recyclerview.widget.DiffUtil 49 import androidx.recyclerview.widget.LinearLayoutManager 50 import androidx.recyclerview.widget.RecyclerView 51 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP 52 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT 53 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS 54 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS 55 import com.android.internal.jank.InteractionJankMonitor 56 import com.android.systemui.Dumpable 57 import com.android.systemui.res.R 58 import com.android.systemui.animation.DialogCuj 59 import com.android.systemui.animation.DialogTransitionAnimator 60 import com.android.systemui.animation.Expandable 61 import com.android.systemui.broadcast.BroadcastDispatcher 62 import com.android.systemui.dagger.SysUISingleton 63 import com.android.systemui.dagger.qualifiers.Background 64 import com.android.systemui.dagger.qualifiers.Main 65 import com.android.systemui.dump.DumpManager 66 import com.android.systemui.settings.UserTracker 67 import com.android.systemui.shared.system.SysUiStatsLog 68 import com.android.systemui.statusbar.phone.SystemUIDialog 69 import com.android.systemui.util.DeviceConfigProxy 70 import com.android.systemui.util.indentIfPossible 71 import com.android.systemui.util.time.SystemClock 72 import java.io.PrintWriter 73 import java.util.Objects 74 import java.util.concurrent.Executor 75 import javax.inject.Inject 76 import kotlin.math.max 77 import kotlinx.coroutines.flow.MutableStateFlow 78 import kotlinx.coroutines.flow.StateFlow 79 import kotlinx.coroutines.flow.asStateFlow 80 81 /** A controller for the dealing with services running in the foreground. */ 82 interface FgsManagerController { 83 84 /** The number of packages with a service running in the foreground. */ 85 val numRunningPackages: Int 86 87 /** 88 * Whether there were new changes to the foreground services since the last [shown][showDialog] 89 * dialog was dismissed. 90 */ 91 val newChangesSinceDialogWasDismissed: Boolean 92 93 /** 94 * Whether we should show a dot to indicate when [newChangesSinceDialogWasDismissed] is true. 95 */ 96 val showFooterDot: StateFlow<Boolean> 97 98 val includesUserVisibleJobs: Boolean 99 100 /** 101 * Initialize this controller. This should be called once, before this controller is used for 102 * the first time. 103 */ 104 fun init() 105 106 /** 107 * Show the foreground services dialog. The dialog will be expanded from [expandable] if 108 * it's not `null`. 109 */ 110 fun showDialog(expandable: Expandable?) 111 112 /** Add a [OnNumberOfPackagesChangedListener]. */ 113 fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) 114 115 /** Remove a [OnNumberOfPackagesChangedListener]. */ 116 fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) 117 118 /** Add a [OnDialogDismissedListener]. */ 119 fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) 120 121 /** Remove a [OnDialogDismissedListener]. */ 122 fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) 123 124 @VisibleForTesting 125 fun visibleButtonsCount(): Int 126 127 interface OnNumberOfPackagesChangedListener { 128 /** Called when [numRunningPackages] changed. */ 129 fun onNumberOfPackagesChanged(numPackages: Int) 130 } 131 132 interface OnDialogDismissedListener { 133 /** Called when a dialog shown using [showDialog] was dismissed. */ 134 fun onDialogDismissed() 135 } 136 } 137 138 @SysUISingleton 139 class FgsManagerControllerImpl @Inject constructor( 140 private val context: Context, 141 @Main private val mainExecutor: Executor, 142 @Background private val backgroundExecutor: Executor, 143 private val systemClock: SystemClock, 144 private val activityManager: IActivityManager, 145 private val jobScheduler: JobScheduler, 146 private val packageManager: PackageManager, 147 private val userTracker: UserTracker, 148 private val deviceConfigProxy: DeviceConfigProxy, 149 private val dialogTransitionAnimator: DialogTransitionAnimator, 150 private val broadcastDispatcher: BroadcastDispatcher, 151 private val dumpManager: DumpManager, 152 private val systemUIDialogFactory: SystemUIDialog.Factory, 153 ) : Dumpable, FgsManagerController { 154 155 companion object { 156 private const val INTERACTION_JANK_TAG = "active_background_apps" 157 private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false 158 private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true 159 private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = true 160 private const val DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP = true 161 } 162 163 override var newChangesSinceDialogWasDismissed = false 164 private set 165 166 val _showFooterDot = MutableStateFlow(false) 167 override val showFooterDot: StateFlow<Boolean> = _showFooterDot.asStateFlow() 168 169 private var showStopBtnForUserAllowlistedApps = false 170 171 private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS 172 173 private var informJobSchedulerOfPendingAppStop = 174 DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP 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 /* Only mutate on the background thread */ 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 informJobSchedulerOfPendingAppStop = deviceConfigProxy.getBoolean( 237 NAMESPACE_SYSTEMUI, 238 TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP, 239 DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP) 240 241 try { 242 activityManager.registerForegroundServiceObserver(foregroundServiceObserver) 243 // Clumping FGS and user-visible jobs here and showing a single entry and button 244 // for them is the easiest way to get user-visible jobs showing in Task Manager. 245 // Ideally, we would have dedicated UI in task manager for the user-visible jobs. 246 // TODO(255768978): distinguish jobs from FGS and give users more control 247 if (showUserVisibleJobs) { 248 jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver) 249 } 250 } catch (e: RemoteException) { 251 e.rethrowFromSystemServer() 252 } 253 254 userTracker.addCallback(userTrackerCallback, backgroundExecutor) 255 256 currentProfileIds.addAll(userTracker.userProfiles.map { it.id }) 257 258 deviceConfigProxy.addOnPropertiesChangedListener( 259 NAMESPACE_SYSTEMUI, 260 backgroundExecutor 261 ) { 262 _showFooterDot.value = 263 it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value) 264 showStopBtnForUserAllowlistedApps = it.getBoolean( 265 TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, 266 showStopBtnForUserAllowlistedApps) 267 var wasShowingUserVisibleJobs = showUserVisibleJobs 268 showUserVisibleJobs = it.getBoolean( 269 TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs) 270 if (showUserVisibleJobs != wasShowingUserVisibleJobs) { 271 onShowUserVisibleJobsFlagChanged() 272 } 273 informJobSchedulerOfPendingAppStop = it.getBoolean( 274 TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, 275 informJobSchedulerOfPendingAppStop) 276 } 277 _showFooterDot.value = deviceConfigProxy.getBoolean( 278 NAMESPACE_SYSTEMUI, 279 TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT 280 ) 281 showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean( 282 NAMESPACE_SYSTEMUI, 283 TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, 284 DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS) 285 286 dumpManager.registerDumpable(this) 287 288 broadcastDispatcher.registerReceiver( 289 object : BroadcastReceiver() { 290 override fun onReceive(context: Context, intent: Intent) { 291 if (intent.action == Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER) { 292 showDialog(null) 293 } 294 } 295 }, 296 IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER), 297 executor = mainExecutor, 298 flags = Context.RECEIVER_NOT_EXPORTED 299 ) 300 301 initialized = true 302 } 303 } 304 305 @GuardedBy("lock") 306 private val onNumberOfPackagesChangedListeners = 307 mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>() 308 309 @GuardedBy("lock") 310 private val onDialogDismissedListeners = 311 mutableSetOf<FgsManagerController.OnDialogDismissedListener>() 312 addOnNumberOfPackagesChangedListenernull313 override fun addOnNumberOfPackagesChangedListener( 314 listener: FgsManagerController.OnNumberOfPackagesChangedListener 315 ) { 316 synchronized(lock) { 317 onNumberOfPackagesChangedListeners.add(listener) 318 } 319 } 320 removeOnNumberOfPackagesChangedListenernull321 override fun removeOnNumberOfPackagesChangedListener( 322 listener: FgsManagerController.OnNumberOfPackagesChangedListener 323 ) { 324 synchronized(lock) { 325 onNumberOfPackagesChangedListeners.remove(listener) 326 } 327 } 328 addOnDialogDismissedListenernull329 override fun addOnDialogDismissedListener( 330 listener: FgsManagerController.OnDialogDismissedListener 331 ) { 332 synchronized(lock) { 333 onDialogDismissedListeners.add(listener) 334 } 335 } 336 removeOnDialogDismissedListenernull337 override fun removeOnDialogDismissedListener( 338 listener: FgsManagerController.OnDialogDismissedListener 339 ) { 340 synchronized(lock) { 341 onDialogDismissedListeners.remove(listener) 342 } 343 } 344 getNumVisiblePackagesLockednull345 private fun getNumVisiblePackagesLocked(): Int { 346 return runningTaskIdentifiers.keys.count { 347 it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId) 348 } 349 } 350 updateNumberOfVisibleRunningPackagesLockednull351 private fun updateNumberOfVisibleRunningPackagesLocked() { 352 val num = getNumVisiblePackagesLocked() 353 if (num != lastNumberOfVisiblePackages) { 354 lastNumberOfVisiblePackages = num 355 newChangesSinceDialogWasDismissed = true 356 onNumberOfPackagesChangedListeners.forEach { 357 backgroundExecutor.execute { 358 it.onNumberOfPackagesChanged(num) 359 } 360 } 361 } 362 } 363 visibleButtonsCountnull364 override fun visibleButtonsCount(): Int { 365 synchronized(lock) { 366 return getNumVisibleButtonsLocked() 367 } 368 } 369 getNumVisibleButtonsLockednull370 private fun getNumVisibleButtonsLocked(): Int { 371 return runningTaskIdentifiers.keys.count { 372 it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId) 373 } 374 } 375 showDialognull376 override fun showDialog(expandable: Expandable?) { 377 synchronized(lock) { 378 if (dialog == null) { 379 val dialog = systemUIDialogFactory.create() 380 dialog.setTitle(R.string.fgs_manager_dialog_title) 381 dialog.setMessage(R.string.fgs_manager_dialog_message) 382 383 val dialogContext = dialog.context 384 385 val recyclerView = RecyclerView(dialogContext) 386 recyclerView.layoutManager = LinearLayoutManager(dialogContext) 387 recyclerView.adapter = appListAdapter 388 389 val topSpacing = dialogContext.resources 390 .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing) 391 dialog.setView(recyclerView, 0, topSpacing, 0, 0) 392 393 this.dialog = dialog 394 395 dialog.setOnDismissListener { 396 newChangesSinceDialogWasDismissed = false 397 synchronized(lock) { 398 this.dialog = null 399 updateAppItemsLocked() 400 } 401 onDialogDismissedListeners.forEach { 402 mainExecutor.execute(it::onDialogDismissed) 403 } 404 } 405 406 mainExecutor.execute { 407 val controller = 408 expandable?.dialogTransitionController( 409 DialogCuj( 410 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, 411 INTERACTION_JANK_TAG, 412 ) 413 ) 414 if (controller != null) { 415 dialogTransitionAnimator.show(dialog, controller) 416 } else { 417 dialog.show() 418 } 419 } 420 421 updateAppItemsLocked(refreshUiControls = true) 422 } 423 } 424 } 425 426 @GuardedBy("lock") updateAppItemsLockednull427 private fun updateAppItemsLocked(refreshUiControls: Boolean = false) { 428 if (dialog == null) { 429 backgroundExecutor.execute { 430 clearRunningApps() 431 } 432 return 433 } 434 435 val packagesToStartTime = runningTaskIdentifiers.mapValues { it.value.startTime } 436 val profileIds = currentProfileIds.toSet() 437 backgroundExecutor.execute { 438 updateAppItems(packagesToStartTime, profileIds, refreshUiControls) 439 } 440 } 441 442 /** 443 * Must be called on the background thread. 444 */ 445 @WorkerThread updateAppItemsnull446 private fun updateAppItems( 447 packages: Map<UserPackage, Long>, 448 profileIds: Set<Int>, 449 refreshUiControls: Boolean = true 450 ) { 451 if (refreshUiControls) { 452 packages.forEach { (pkg, _) -> 453 pkg.updateUiControl() 454 } 455 } 456 457 val addedPackages = packages.keys.filter { 458 profileIds.contains(it.userId) && 459 it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true 460 } 461 val removedPackages = runningApps.keys.filter { it !in packages } 462 463 addedPackages.forEach { 464 val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) 465 runningApps[it] = RunningApp( 466 it.userId, it.packageName, 467 packages[it]!!, it.uiControl, 468 packageManager.getApplicationLabel(ai), 469 packageManager.getUserBadgedIcon( 470 packageManager.getApplicationIcon(ai), UserHandle.of(it.userId) 471 ) 472 ) 473 logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted) 474 } 475 476 removedPackages.forEach { pkg -> 477 val ra = runningApps[pkg]!! 478 val ra2 = ra.copy().also { 479 it.stopped = true 480 it.appLabel = ra.appLabel 481 it.icon = ra.icon 482 } 483 runningApps[pkg] = ra2 484 } 485 486 mainExecutor.execute { 487 appListAdapter 488 .setData(runningApps.values.toList().sortedByDescending { it.timeStarted }) 489 } 490 } 491 492 /** 493 * Must be called on the background thread. 494 */ 495 @WorkerThread clearRunningAppsnull496 private fun clearRunningApps() { 497 runningApps.clear() 498 } 499 stopPackagenull500 private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) { 501 logEvent(stopped = true, packageName, userId, timeStarted) 502 val userPackageKey = UserPackage(userId, packageName) 503 if (showUserVisibleJobs || informJobSchedulerOfPendingAppStop) { 504 // TODO(255768978): allow fine-grained job control 505 jobScheduler.notePendingUserRequestedAppStop(packageName, userId, "task manager") 506 } 507 activityManager.stopAppForUser(packageName, userId) 508 } 509 onShowUserVisibleJobsFlagChangednull510 private fun onShowUserVisibleJobsFlagChanged() { 511 if (showUserVisibleJobs) { 512 jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver) 513 } else { 514 jobScheduler.unregisterUserVisibleJobObserver(userVisibleJobObserver) 515 516 synchronized(lock) { 517 for ((userPackage, startTimeAndIdentifiers) in runningTaskIdentifiers) { 518 if (startTimeAndIdentifiers.hasFgs()) { 519 // The app still has FGS running, so all we need to do is remove 520 // the job summaries 521 startTimeAndIdentifiers.clearJobSummaries() 522 } else { 523 // The app only has user-visible jobs running, so remove it from 524 // the map altogether 525 runningTaskIdentifiers.remove(userPackage) 526 } 527 } 528 529 updateNumberOfVisibleRunningPackagesLocked() 530 531 updateAppItemsLocked() 532 } 533 } 534 } 535 logEventnull536 private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) { 537 val timeLogged = systemClock.elapsedRealtime() 538 val event = if (stopped) { 539 SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED 540 } else { 541 SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED 542 } 543 backgroundExecutor.execute { 544 val uid = packageManager.getPackageUidAsUser(packageName, userId) 545 SysUiStatsLog.write( 546 SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event, 547 timeLogged - timeStarted 548 ) 549 } 550 } 551 552 private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() { 553 private val lock = Any() 554 555 @GuardedBy("lock") 556 private var data: List<RunningApp> = listOf() 557 onCreateViewHoldernull558 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder { 559 return AppItemViewHolder( 560 LayoutInflater.from(parent.context) 561 .inflate(R.layout.fgs_manager_app_item, parent, false) 562 ) 563 } 564 onBindViewHoldernull565 override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) { 566 var runningApp: RunningApp 567 synchronized(lock) { 568 runningApp = data[position] 569 } 570 with(holder) { 571 iconView.setImageDrawable(runningApp.icon) 572 appLabelView.text = runningApp.appLabel 573 durationView.text = DateUtils.formatDuration( 574 max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000), 575 DateUtils.LENGTH_MEDIUM 576 ) 577 stopButton.setOnClickListener { 578 stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label) 579 stopPackage(runningApp.userId, runningApp.packageName, runningApp.timeStarted) 580 } 581 if (runningApp.uiControl == UIControl.HIDE_BUTTON) { 582 stopButton.visibility = View.INVISIBLE 583 } 584 if (runningApp.stopped) { 585 stopButton.isEnabled = false 586 stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label) 587 durationView.visibility = View.GONE 588 } else { 589 stopButton.isEnabled = true 590 stopButton.setText(R.string.fgs_manager_app_item_stop_button_label) 591 durationView.visibility = View.VISIBLE 592 } 593 } 594 } 595 getItemCountnull596 override fun getItemCount(): Int { 597 return data.size 598 } 599 setDatanull600 fun setData(newData: List<RunningApp>) { 601 var oldData = data 602 data = newData 603 604 DiffUtil.calculateDiff(object : DiffUtil.Callback() { 605 override fun getOldListSize(): Int { 606 return oldData.size 607 } 608 609 override fun getNewListSize(): Int { 610 return newData.size 611 } 612 613 override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 614 return oldData[oldItemPosition] == newData[newItemPosition] 615 } 616 617 override fun areContentsTheSame( 618 oldItemPosition: Int, 619 newItemPosition: Int 620 ): Boolean { 621 return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped 622 } 623 }).dispatchUpdatesTo(this) 624 } 625 } 626 627 private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() { onForegroundStateChangednull628 override fun onForegroundStateChanged( 629 token: IBinder, 630 packageName: String, 631 userId: Int, 632 isForeground: Boolean 633 ) { 634 synchronized(lock) { 635 val userPackageKey = UserPackage(userId, packageName) 636 if (isForeground) { 637 runningTaskIdentifiers 638 .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) } 639 .addFgsToken(token) 640 } else { 641 if (runningTaskIdentifiers[userPackageKey]?.also { 642 it.removeFgsToken(token) 643 }?.isEmpty() == true 644 ) { 645 runningTaskIdentifiers.remove(userPackageKey) 646 } 647 } 648 649 updateNumberOfVisibleRunningPackagesLocked() 650 651 updateAppItemsLocked() 652 } 653 } 654 } 655 656 private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() { onUserVisibleJobStateChangednull657 override fun onUserVisibleJobStateChanged( 658 summary: UserVisibleJobSummary, 659 isRunning: Boolean 660 ) { 661 synchronized(lock) { 662 val userPackageKey = UserPackage( 663 UserHandle.getUserId(summary.callingUid), summary.callingPackageName) 664 if (isRunning) { 665 runningTaskIdentifiers 666 .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) } 667 .addJobSummary(summary) 668 } else { 669 if (runningTaskIdentifiers[userPackageKey]?.also { 670 it.removeJobSummary(summary) 671 }?.isEmpty() == true 672 ) { 673 runningTaskIdentifiers.remove(userPackageKey) 674 } 675 } 676 677 updateNumberOfVisibleRunningPackagesLocked() 678 679 updateAppItemsLocked() 680 } 681 } 682 } 683 684 private inner class UserPackage( 685 val userId: Int, 686 val packageName: String 687 ) { <lambda>null688 val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) } 689 var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED 690 691 private var uiControlInitialized = false 692 var uiControl: UIControl = UIControl.NORMAL 693 get() { 694 if (!uiControlInitialized) { 695 updateUiControl() 696 } 697 return field 698 } 699 private set 700 updateUiControlnull701 fun updateUiControl() { 702 backgroundRestrictionExemptionReason = 703 activityManager.getBackgroundRestrictionExemptionReason(uid) 704 uiControl = when (backgroundRestrictionExemptionReason) { 705 PowerExemptionManager.REASON_SYSTEM_UID, 706 PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY 707 708 PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED, 709 PowerExemptionManager.REASON_DEVICE_OWNER, 710 PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL, 711 PowerExemptionManager.REASON_DPO_PROTECTED_APP, 712 PowerExemptionManager.REASON_PROFILE_OWNER, 713 PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN, 714 PowerExemptionManager.REASON_PROC_STATE_PERSISTENT, 715 PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI, 716 PowerExemptionManager.REASON_ROLE_DIALER, 717 PowerExemptionManager.REASON_SYSTEM_MODULE, 718 PowerExemptionManager.REASON_SYSTEM_EXEMPT_APP_OP -> UIControl.HIDE_BUTTON 719 720 PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE -> 721 if (showStopBtnForUserAllowlistedApps) { 722 UIControl.NORMAL 723 } else { 724 UIControl.HIDE_BUTTON 725 } 726 else -> UIControl.NORMAL 727 } 728 uiControlInitialized = true 729 } 730 equalsnull731 override fun equals(other: Any?): Boolean { 732 if (other !is UserPackage) { 733 return false 734 } 735 return other.packageName == packageName && other.userId == userId 736 } 737 hashCodenull738 override fun hashCode(): Int = Objects.hash(userId, packageName) 739 740 fun dump(pw: PrintWriter) { 741 pw.println("UserPackage: [") 742 pw.indentIfPossible { 743 pw.println("userId=$userId") 744 pw.println("packageName=$packageName") 745 pw.println("uiControl=$uiControl (reason=$backgroundRestrictionExemptionReason)") 746 } 747 pw.println("]") 748 } 749 } 750 751 private data class StartTimeAndIdentifiers( 752 val systemClock: SystemClock 753 ) { 754 val startTime = systemClock.elapsedRealtime() 755 val fgsTokens = mutableSetOf<IBinder>() 756 val jobSummaries = mutableSetOf<UserVisibleJobSummary>() 757 addJobSummarynull758 fun addJobSummary(summary: UserVisibleJobSummary) { 759 jobSummaries.add(summary) 760 } 761 clearJobSummariesnull762 fun clearJobSummaries() { 763 jobSummaries.clear() 764 } 765 removeJobSummarynull766 fun removeJobSummary(summary: UserVisibleJobSummary) { 767 jobSummaries.remove(summary) 768 } 769 addFgsTokennull770 fun addFgsToken(token: IBinder) { 771 fgsTokens.add(token) 772 } 773 removeFgsTokennull774 fun removeFgsToken(token: IBinder) { 775 fgsTokens.remove(token) 776 } 777 hasFgsnull778 fun hasFgs(): Boolean { 779 return !fgsTokens.isEmpty() 780 } 781 hasRunningJobsnull782 fun hasRunningJobs(): Boolean { 783 return !jobSummaries.isEmpty() 784 } 785 isEmptynull786 fun isEmpty(): Boolean { 787 return fgsTokens.isEmpty() && jobSummaries.isEmpty() 788 } 789 dumpnull790 fun dump(pw: PrintWriter) { 791 pw.println("StartTimeAndIdentifiers: [") 792 pw.indentIfPossible { 793 pw.println( 794 "startTime=$startTime (time running =" + 795 " ${systemClock.elapsedRealtime() - startTime}ms)" 796 ) 797 pw.println("fgs tokens: [") 798 pw.indentIfPossible { 799 for (token in fgsTokens) { 800 pw.println("$token") 801 } 802 } 803 pw.println("job summaries: [") 804 pw.indentIfPossible { 805 for (summary in jobSummaries) { 806 pw.println("$summary") 807 } 808 } 809 pw.println("]") 810 } 811 pw.println("]") 812 } 813 } 814 815 private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) { 816 val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label) 817 val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration) 818 val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon) 819 val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button) 820 } 821 822 private data class RunningApp( 823 val userId: Int, 824 val packageName: String, 825 val timeStarted: Long, 826 val uiControl: UIControl 827 ) { 828 constructor( 829 userId: Int, 830 packageName: String, 831 timeStarted: Long, 832 uiControl: UIControl, 833 appLabel: CharSequence, 834 icon: Drawable 835 ) : this(userId, packageName, timeStarted, uiControl) { 836 this.appLabel = appLabel 837 this.icon = icon 838 } 839 840 // variables to keep out of the generated equals() 841 var appLabel: CharSequence = "" 842 var icon: Drawable? = null 843 var stopped = false 844 dumpnull845 fun dump(pw: PrintWriter, systemClock: SystemClock) { 846 pw.println("RunningApp: [") 847 pw.indentIfPossible { 848 pw.println("userId=$userId") 849 pw.println("packageName=$packageName") 850 pw.println( 851 "timeStarted=$timeStarted (time since start =" + 852 " ${systemClock.elapsedRealtime() - timeStarted}ms)" 853 ) 854 pw.println("uiControl=$uiControl") 855 pw.println("appLabel=$appLabel") 856 pw.println("icon=$icon") 857 pw.println("stopped=$stopped") 858 } 859 pw.println("]") 860 } 861 } 862 863 private enum class UIControl { 864 NORMAL, HIDE_BUTTON, HIDE_ENTRY 865 } 866 dumpnull867 override fun dump(printwriter: PrintWriter, args: Array<out String>) { 868 val pw = IndentingPrintWriter(printwriter) 869 synchronized(lock) { 870 pw.println("current user profiles = $currentProfileIds") 871 pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed") 872 pw.println("Running task identifiers: [") 873 pw.indentIfPossible { 874 runningTaskIdentifiers.forEach { (userPackage, startTimeAndIdentifiers) -> 875 pw.println("{") 876 pw.indentIfPossible { 877 userPackage.dump(pw) 878 startTimeAndIdentifiers.dump(pw) 879 } 880 pw.println("}") 881 } 882 } 883 pw.println("]") 884 885 pw.println("Loaded package UI info: [") 886 pw.indentIfPossible { 887 runningApps.forEach { (userPackage, runningApp) -> 888 pw.println("{") 889 pw.indentIfPossible { 890 userPackage.dump(pw) 891 runningApp.dump(pw, systemClock) 892 } 893 pw.println("}") 894 } 895 } 896 pw.println("]") 897 } 898 } 899 } 900