Update when media controls get cleared

Some apps include an action to dismiss a media notification, so we
should listen for that happening and clear controls in that case.

Also, remove STATE_CONNECTING as a condition to clear controls -
This was originally added in ag/11056932 as a workaround for an issue
with YouTube cast sessions. However this caused issues with other apps
like Spotify which set STATE_CONNECTING while still active. YT was using
that as a workaround for legacy behavior and will update to use
STATE_NONE for R+ builds (b/155213698). In the meantime, listening for
when the notification is removed will also work to clear YT's controls
as expected.

Fixes: 154953276
Fixes: 155029855
Test: manual

Change-Id: Ie9320e1406c1f457a39f67705ec1ffcb3a983488
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index ddc9c9d..5ccfd8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -124,11 +124,7 @@
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             final int s = state != null ? state.getState() : PlaybackState.STATE_NONE;
-            // When the playback state is NONE or CONNECTING, transition the player to the
-            // resumption state. State CONNECTING needs to be considered for Cast sessions. Ending
-            // a cast session in YT results in the CONNECTING state, which makes sense if you
-            // thinking of the session as waiting to connect to another cast device.
-            if (s == PlaybackState.STATE_NONE || s == PlaybackState.STATE_CONNECTING) {
+            if (s == PlaybackState.STATE_NONE) {
                 Log.d(TAG, "playback state change will trigger resumption, state=" + state);
                 clearControls();
                 makeInactive();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
index 302b8420..9e53286 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
@@ -82,7 +82,7 @@
         public void onChildrenLoaded(String parentId,
                 List<MediaBrowser.MediaItem> children) {
             if (children.size() == 0) {
-                Log.e(TAG, "No children found");
+                Log.e(TAG, "No children found for " + mComponentName);
                 return;
             }
             // We ask apps to return a playable item as the first child when sending
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index cb3d511..121e2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -53,6 +53,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.NotificationVisibility;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.InfoMediaManager;
@@ -75,6 +76,9 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
 import com.android.systemui.tuner.TunerService;
@@ -116,6 +120,7 @@
     private final DelayableExecutor mBackgroundExecutor;
     private boolean mUpdateCarousel = false;
     private ActivityStarter mActivityStarter;
+    private NotificationEntryManager mNotificationEntryManager;
 
     protected boolean mExpanded;
     protected boolean mListening;
@@ -151,6 +156,15 @@
         }
     };
 
+    private final NotificationEntryListener mNotificationEntryListener =
+            new NotificationEntryListener() {
+        @Override
+        public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
+                boolean removedByUser, int reason) {
+            checkToRemoveMediaNotification(entry);
+        }
+    };
+
     @Inject
     public QSPanel(
             @Named(VIEW_CONTEXT) Context context,
@@ -161,7 +175,8 @@
             @Main Executor foregroundExecutor,
             @Background DelayableExecutor backgroundExecutor,
             @Nullable LocalBluetoothManager localBluetoothManager,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            NotificationEntryManager entryManager
     ) {
         super(context, attrs);
         mContext = context;
@@ -172,6 +187,7 @@
         mLocalBluetoothManager = localBluetoothManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mActivityStarter = activityStarter;
+        mNotificationEntryManager = entryManager;
 
         setOrientation(VERTICAL);
 
@@ -407,6 +423,27 @@
         mHasLoadedMediaControls = true;
     }
 
+    private void checkToRemoveMediaNotification(NotificationEntry entry) {
+        if (!useQsMediaPlayer(mContext)) {
+            return;
+        }
+
+        if (!entry.isMediaNotification()) {
+            return;
+        }
+
+        // If this entry corresponds to an existing set of controls, clear the controls
+        // This will handle apps that use an action to clear their notification
+        for (QSMediaPlayer p : mMediaPlayers) {
+            if (p.getKey() != null && p.getKey().equals(entry.getKey())) {
+                Log.d(TAG, "Clearing controls since notification removed " + entry.getKey());
+                p.clearControls();
+                return;
+            }
+        }
+        Log.d(TAG, "Media notification removed but no player found " + entry.getKey());
+    }
+
     protected void addDivider() {
         mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
         mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -473,6 +510,7 @@
                 loadMediaResumptionControls();
             }
         }
+        mNotificationEntryManager.addNotificationEntryListener(mNotificationEntryListener);
     }
 
     @Override
@@ -489,6 +527,7 @@
         }
         mDumpManager.unregisterDumpable(getDumpableTag());
         mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
+        mNotificationEntryManager.removeNotificationEntryListener(mNotificationEntryListener);
         super.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index becf9da..38dea65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.Utils;
@@ -86,10 +87,12 @@
             @Main Executor foregroundExecutor,
             @Background DelayableExecutor backgroundExecutor,
             @Nullable LocalBluetoothManager localBluetoothManager,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            NotificationEntryManager entryManager
     ) {
         super(context, attrs, dumpManager, broadcastDispatcher, qsLogger,
-                foregroundExecutor, backgroundExecutor, localBluetoothManager, activityStarter);
+                foregroundExecutor, backgroundExecutor, localBluetoothManager, activityStarter,
+                entryManager);
         if (mFooter != null) {
             removeView(mFooter.getView());
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 9a32b1d..392adf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.junit.Before;
@@ -91,6 +92,8 @@
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
     private ActivityStarter mActivityStarter;
+    @Mock
+    private NotificationEntryManager mEntryManager;
 
     @Before
     public void setup() throws Exception {
@@ -101,7 +104,7 @@
             mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
             mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
                     mQSLogger, mForegroundExecutor, mBackgroundExecutor,
-                    mLocalBluetoothManager, mActivityStarter);
+                    mLocalBluetoothManager, mActivityStarter, mEntryManager);
             // Provides a parent with non-zero size for QSPanel
             mParentView = new FrameLayout(mContext);
             mParentView.addView(mQsPanel);