Send media controller code to background thread.

Flag: com.android.systemui.notification_media_manager_background_execution
Bug: 336612071
Test: manual.
Test: atest SystemUiRoboTests:NotificationMediaManagerTest
Change-Id: I8c3f6fbcfdf94d87ed5a9c3f2de8035ef49680c9
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 36bad5e..9c20345 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1012,3 +1012,13 @@
      purpose: PURPOSE_BUGFIX
    }
 }
+
+flag {
+  name: "notification_media_manager_background_execution"
+  namespace: "systemui"
+  description: "Decide whether to execute binder calls in background thread"
+  bug: "336612071"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
index a67a8ab..fed6131 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar
 
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.fakeExecutorHandler
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.service.notification.NotificationListenerService
@@ -54,6 +58,7 @@
     private val notifPipeline = kosmos.notifPipeline
     private val notifCollection = kosmos.mockNotifCollection
     private val dumpManager = kosmos.dumpManager
+    private val handler = kosmos.fakeExecutorHandler
     private val mediaDataManager = mock<MediaDataManager>()
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
 
@@ -72,7 +77,11 @@
                 mediaDataManager,
                 dumpManager,
                 backgroundExecutor,
+                handler,
             )
+        val mediaSession = MediaSession(context, "TEST")
+        notificationMediaManager.mMediaController =
+            MediaController(context, mediaSession.sessionToken)
 
         verify(mediaDataManager).addListener(listenerCaptor.capture())
     }
@@ -114,4 +123,32 @@
         verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
         assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION)
+    fun clearMediaNotification_flagOn_resetMediaMetadata() {
+        // set up media metadata.
+        notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build())
+        backgroundExecutor.runAllReady()
+
+        // clear media notification.
+        notificationMediaManager.clearCurrentMediaNotification()
+        backgroundExecutor.runAllReady()
+
+        assertThat(notificationMediaManager.mediaMetadata).isNull()
+        assertThat(notificationMediaManager.mMediaController).isNull()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION)
+    fun clearMediaNotification_flagOff_resetMediaMetadata() {
+        // set up media metadata.
+        notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build())
+
+        // clear media notification.
+        notificationMediaManager.clearCurrentMediaNotification()
+
+        assertThat(notificationMediaManager.mediaMetadata).isNull()
+        assertThat(notificationMediaManager.mMediaController).isNull()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 03c6670..6d34a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar;
 
 import static com.android.systemui.Flags.mediaControlsUserInitiatedDeleteintent;
+import static com.android.systemui.Flags.notificationMediaManagerBackgroundExecution;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,12 +27,16 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.os.Handler;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.shared.model.MediaData;
@@ -48,6 +53,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -80,13 +86,16 @@
     private final ArrayList<MediaListener> mMediaListeners;
 
     private final Executor mBackgroundExecutor;
+    private final Handler mHandler;
 
     protected NotificationPresenter mPresenter;
-    private MediaController mMediaController;
+    @VisibleForTesting
+    MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
 
-    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
+    @VisibleForTesting
+    final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             super.onPlaybackStateChanged(state);
@@ -107,11 +116,20 @@
             if (DEBUG_MEDIA) {
                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
             }
-            mMediaMetadata = metadata;
+            if (notificationMediaManagerBackgroundExecution()) {
+                mBackgroundExecutor.execute(() -> setMediaMetadata(metadata));
+            } else {
+                setMediaMetadata(metadata);
+            }
+
             dispatchUpdateMediaMetaData();
         }
     };
 
+    private void setMediaMetadata(MediaMetadata metadata) {
+        mMediaMetadata = metadata;
+    }
+
     /**
      * Injected constructor. See {@link CentralSurfacesModule}.
      */
@@ -122,7 +140,9 @@
             NotifCollection notifCollection,
             MediaDataManager mediaDataManager,
             DumpManager dumpManager,
-            @Background Executor backgroundExecutor) {
+            @Background Executor backgroundExecutor,
+            @Main Handler handler
+    ) {
         mContext = context;
         mMediaListeners = new ArrayList<>();
         mVisibilityProvider = visibilityProvider;
@@ -130,6 +150,7 @@
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
         mBackgroundExecutor = backgroundExecutor;
+        mHandler = handler;
 
         setupNotifPipeline();
 
@@ -262,6 +283,14 @@
 
     public void addCallback(MediaListener callback) {
         mMediaListeners.add(callback);
+        if (notificationMediaManagerBackgroundExecution()) {
+            mBackgroundExecutor.execute(() -> updateMediaMetaData(callback));
+        } else {
+            updateMediaMetaData(callback);
+        }
+    }
+
+    private void updateMediaMetaData(MediaListener callback) {
         callback.onPrimaryMetadataOrStateChanged(mMediaMetadata,
                 getMediaControllerPlaybackState(mMediaController));
     }
@@ -273,7 +302,11 @@
     public void findAndUpdateMediaNotifications() {
         // TODO(b/169655907): get the semi-filtered notifications for current user
         Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
-        findPlayingMediaNotification(allNotifications);
+        if (notificationMediaManagerBackgroundExecution()) {
+            mBackgroundExecutor.execute(() -> findPlayingMediaNotification(allNotifications));
+        } else {
+            findPlayingMediaNotification(allNotifications);
+        }
         dispatchUpdateMediaMetaData();
     }
 
@@ -312,7 +345,7 @@
             // We have a new media session
             clearCurrentMediaNotificationSession();
             mMediaController = controller;
-            mMediaController.registerCallback(mMediaListener);
+            mMediaController.registerCallback(mMediaListener, mHandler);
             mMediaMetadata = mMediaController.getMetadata();
             if (DEBUG_MEDIA) {
                 Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
@@ -331,13 +364,29 @@
     }
 
     public void clearCurrentMediaNotification() {
+        if (notificationMediaManagerBackgroundExecution()) {
+            mBackgroundExecutor.execute(this::clearMediaNotification);
+        } else {
+            clearMediaNotification();
+        }
+    }
+
+    private void clearMediaNotification() {
         mMediaNotificationKey = null;
         clearCurrentMediaNotificationSession();
     }
 
     private void dispatchUpdateMediaMetaData() {
-        @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
         ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
+        if (notificationMediaManagerBackgroundExecution()) {
+            mBackgroundExecutor.execute(() -> updateMediaMetaData(callbacks));
+        } else {
+            updateMediaMetaData(callbacks);
+        }
+    }
+
+    private void updateMediaMetaData(List<MediaListener> callbacks) {
+        @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
         for (int i = 0; i < callbacks.size(); i++) {
             callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state);
         }
@@ -393,7 +442,6 @@
                 Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
                         + mMediaController.getPackageName());
             }
-            // TODO(b/336612071): move to background thread
             mMediaController.unregisterCallback(mMediaListener);
         }
         mMediaController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 0524589..7df7ef1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
 
 import android.content.Context;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
 import android.util.Log;
@@ -99,7 +100,8 @@
             NotifCollection notifCollection,
             MediaDataManager mediaDataManager,
             DumpManager dumpManager,
-            @Background Executor backgroundExecutor) {
+            @Background Executor backgroundExecutor,
+            @Main Handler handler) {
         return new NotificationMediaManager(
                 context,
                 visibilityProvider,
@@ -107,7 +109,8 @@
                 notifCollection,
                 mediaDataManager,
                 dumpManager,
-                backgroundExecutor);
+                backgroundExecutor,
+                handler);
     }
 
     /** */