• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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