Merge "Revert^2 "Use ConcurrentHashMap to make private maps thread safe"" into main
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index a01ceb2..d073580 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -56,6 +56,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
@@ -76,7 +77,8 @@
     private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
             new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
 
-    private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>();
+    private final ConcurrentLinkedQueue<TaskVisualsChangeListener> mThumbnailChangeListeners =
+            new ConcurrentLinkedQueue<>();
     private final Context mContext;
 
     private final RecentTasksList mTaskList;
@@ -239,8 +241,8 @@
     public boolean onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
         mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
 
-        for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
-            Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);
+        for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
+            Task task = listener.onTaskThumbnailChanged(taskId, snapshot);
             if (task != null) {
                 task.thumbnail = snapshot;
             }
@@ -269,8 +271,8 @@
     @Override
     public void onAppIconChanged(String packageName, UserHandle user) {
         mIconCache.invalidateCacheEntries(packageName, user);
-        for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
-            mThumbnailChangeListeners.get(i).onTaskIconChanged(packageName, user);
+        for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
+            listener.onTaskIconChanged(packageName, user);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
index a141e89..a45d194 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
@@ -23,6 +23,7 @@
 import com.android.quickstep.util.TaskVisualsChangeListener
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
+import java.util.concurrent.ConcurrentHashMap
 
 /** Delegates the checking of task visuals (thumbnails, high res changes, icons) */
 interface TaskVisualsChangedDelegate :
@@ -30,7 +31,7 @@
     /** Registers a callback for visuals relating to icons */
     fun registerTaskIconChangedCallback(
         taskKey: Task.TaskKey,
-        taskIconChangedCallback: TaskIconChangedCallback
+        taskIconChangedCallback: TaskIconChangedCallback,
     )
 
     /** Unregisters a callback for visuals relating to icons */
@@ -39,7 +40,7 @@
     /** Registers a callback for visuals relating to thumbnails */
     fun registerTaskThumbnailChangedCallback(
         taskKey: Task.TaskKey,
-        taskThumbnailChangedCallback: TaskThumbnailChangedCallback
+        taskThumbnailChangedCallback: TaskThumbnailChangedCallback,
     )
 
     /** Unregisters a callback for visuals relating to thumbnails */
@@ -66,31 +67,9 @@
     private val highResLoadingStateNotifier: HighResLoadingStateNotifier,
 ) : TaskVisualsChangedDelegate {
     private val taskIconChangedCallbacks =
-        mutableMapOf<Int, Pair<Task.TaskKey, TaskIconChangedCallback>>()
+        ConcurrentHashMap<Int, Pair<Task.TaskKey, TaskIconChangedCallback>>()
     private val taskThumbnailChangedCallbacks =
-        mutableMapOf<Int, Pair<Task.TaskKey, TaskThumbnailChangedCallback>>()
-    private var isListening = false
-
-    @Synchronized
-    private fun onCallbackRegistered() {
-        if (isListening) return
-
-        taskVisualsChangeNotifier.addThumbnailChangeListener(this)
-        highResLoadingStateNotifier.addCallback(this)
-        isListening = true
-    }
-
-    @Synchronized
-    private fun onCallbackUnregistered() {
-        if (!isListening) return
-
-        if (taskIconChangedCallbacks.size + taskThumbnailChangedCallbacks.size == 0) {
-            taskVisualsChangeNotifier.removeThumbnailChangeListener(this)
-            highResLoadingStateNotifier.removeCallback(this)
-        }
-
-        isListening = false
-    }
+        ConcurrentHashMap<Int, Pair<Task.TaskKey, TaskThumbnailChangedCallback>>()
 
     override fun onTaskIconChanged(taskId: Int) {
         taskIconChangedCallbacks[taskId]?.let { (_, callback) -> callback.onTaskIconChanged() }
@@ -119,27 +98,48 @@
 
     override fun registerTaskIconChangedCallback(
         taskKey: Task.TaskKey,
-        taskIconChangedCallback: TaskIconChangedCallback
+        taskIconChangedCallback: TaskIconChangedCallback,
     ) {
-        taskIconChangedCallbacks[taskKey.id] = taskKey to taskIconChangedCallback
-        onCallbackRegistered()
+        updateCallbacks {
+            taskIconChangedCallbacks[taskKey.id] = taskKey to taskIconChangedCallback
+        }
     }
 
     override fun unregisterTaskIconChangedCallback(taskKey: Task.TaskKey) {
-        taskIconChangedCallbacks.remove(taskKey.id)
-        onCallbackUnregistered()
+        updateCallbacks { taskIconChangedCallbacks.remove(taskKey.id) }
     }
 
     override fun registerTaskThumbnailChangedCallback(
         taskKey: Task.TaskKey,
-        taskThumbnailChangedCallback: TaskThumbnailChangedCallback
+        taskThumbnailChangedCallback: TaskThumbnailChangedCallback,
     ) {
-        taskThumbnailChangedCallbacks[taskKey.id] = taskKey to taskThumbnailChangedCallback
-        onCallbackRegistered()
+        updateCallbacks {
+            taskThumbnailChangedCallbacks[taskKey.id] = taskKey to taskThumbnailChangedCallback
+        }
     }
 
     override fun unregisterTaskThumbnailChangedCallback(taskKey: Task.TaskKey) {
-        taskThumbnailChangedCallbacks.remove(taskKey.id)
-        onCallbackUnregistered()
+        updateCallbacks { taskThumbnailChangedCallbacks.remove(taskKey.id) }
+    }
+
+    @Synchronized
+    private fun updateCallbacks(callbackModifier: () -> Unit) {
+        val prevHasCallbacks =
+            taskIconChangedCallbacks.size + taskThumbnailChangedCallbacks.size > 0
+        callbackModifier()
+
+        val currHasCallbacks =
+            taskIconChangedCallbacks.size + taskThumbnailChangedCallbacks.size > 0
+
+        when {
+            prevHasCallbacks && !currHasCallbacks -> {
+                taskVisualsChangeNotifier.removeThumbnailChangeListener(this)
+                highResLoadingStateNotifier.removeCallback(this)
+            }
+            !prevHasCallbacks && currHasCallbacks -> {
+                taskVisualsChangeNotifier.addThumbnailChangeListener(this)
+                highResLoadingStateNotifier.addCallback(this)
+            }
+        }
     }
 }