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