Update FgsManager UI information on bg thread without lock
ActivityManagerService blocks on an awaiting incoming call while at the
same time requiring that the lock be held to provide information used to
make UI decisions. Here we copy data about which tasks are running and
who the current profiles are under then lock then post to the background
thread to load any needed UI data before updating the dialog on the main
thread.
Test: FgsManagerControllerTest
Bug: 276882028
Change-Id: I53664adbb534b05de8b130ed06fca0535f683156
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 0641eec..a3b901b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -44,6 +44,7 @@
import android.widget.TextView
import androidx.annotation.GuardedBy
import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -201,7 +202,7 @@
@GuardedBy("lock")
private val appListAdapter: AppListAdapter = AppListAdapter()
- @GuardedBy("lock")
+ /* Only mutate on the background thread */
private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap()
private val userTrackerCallback = object : UserTracker.Callback {
@@ -374,11 +375,6 @@
override fun showDialog(expandable: Expandable?) {
synchronized(lock) {
if (dialog == null) {
-
- runningTaskIdentifiers.keys.forEach {
- it.updateUiControl()
- }
-
val dialog = SystemUIDialog(context)
dialog.setTitle(R.string.fgs_manager_dialog_title)
dialog.setMessage(R.string.fgs_manager_dialog_message)
@@ -421,33 +417,53 @@
}
}
- backgroundExecutor.execute {
- synchronized(lock) {
- updateAppItemsLocked()
- }
- }
+ updateAppItemsLocked(refreshUiControls = true)
}
}
}
@GuardedBy("lock")
- private fun updateAppItemsLocked() {
+ private fun updateAppItemsLocked(refreshUiControls: Boolean = false) {
if (dialog == null) {
- runningApps.clear()
+ backgroundExecutor.execute {
+ clearRunningApps()
+ }
return
}
- val addedPackages = runningTaskIdentifiers.keys.filter {
- currentProfileIds.contains(it.userId) &&
+ val packagesToStartTime = runningTaskIdentifiers.mapValues { it.value.startTime }
+ val profileIds = currentProfileIds.toSet()
+ backgroundExecutor.execute {
+ updateAppItems(packagesToStartTime, profileIds, refreshUiControls)
+ }
+ }
+
+ /**
+ * Must be called on the background thread.
+ */
+ @WorkerThread
+ private fun updateAppItems(
+ packages: Map<UserPackage, Long>,
+ profileIds: Set<Int>,
+ refreshUiControls: Boolean = true
+ ) {
+ if (refreshUiControls) {
+ packages.forEach { (pkg, _) ->
+ pkg.updateUiControl()
+ }
+ }
+
+ val addedPackages = packages.keys.filter {
+ profileIds.contains(it.userId) &&
it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
}
- val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) }
+ val removedPackages = runningApps.keys.filter { it !in packages }
addedPackages.forEach {
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
runningApps[it] = RunningApp(
it.userId, it.packageName,
- runningTaskIdentifiers[it]!!.startTime, it.uiControl,
+ packages[it]!!, it.uiControl,
packageManager.getApplicationLabel(ai),
packageManager.getUserBadgedIcon(
packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
@@ -472,6 +488,14 @@
}
}
+ /**
+ * Must be called on the background thread.
+ */
+ @WorkerThread
+ private fun clearRunningApps() {
+ runningApps.clear()
+ }
+
private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
logEvent(stopped = true, packageName, userId, timeStarted)
val userPackageKey = UserPackage(userId, packageName)