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