Merge "Fix output switcher will show 2 media session when remote playing" into rvc-qpr-dev
diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java
index 1288cf5..f8e40cd 100644
--- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java
+++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java
@@ -30,6 +30,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -51,6 +52,9 @@
 public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
         implements LocalMediaManager.DeviceCallback {
 
+    private static final String TAG = "MediaDeviceUpdateWorker";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     protected final Context mContext;
     protected final Collection<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
     private final DevicesChangedBroadcastReceiver mReceiver;
@@ -213,6 +217,10 @@
         final List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
         for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) {
             if (!info.isSystemSession()) {
+                if (DEBUG) {
+                    Log.d(TAG, "getActiveRemoteMediaDevice() info : " + info.toString()
+                            + ", package name : " + info.getClientPackageName());
+                }
                 sessionInfos.add(info);
             }
         }
diff --git a/src/com/android/settings/media/MediaOutputIndicatorWorker.java b/src/com/android/settings/media/MediaOutputIndicatorWorker.java
index f094d47..ef3a7bc 100644
--- a/src/com/android/settings/media/MediaOutputIndicatorWorker.java
+++ b/src/com/android/settings/media/MediaOutputIndicatorWorker.java
@@ -25,7 +25,6 @@
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
 import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
@@ -53,6 +52,7 @@
         LocalMediaManager.DeviceCallback {
 
     private static final String TAG = "MediaOutputIndWorker";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final DevicesChangedBroadcastReceiver mReceiver;
     private final Context mContext;
@@ -127,24 +127,8 @@
 
     @Nullable
     MediaController getActiveLocalMediaController() {
-        final MediaSessionManager mMediaSessionManager = mContext.getSystemService(
-                MediaSessionManager.class);
-
-        for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
-            final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
-            if (pi == null) {
-                return null;
-            }
-            final PlaybackState playbackState = controller.getPlaybackState();
-            if (playbackState == null) {
-                return null;
-            }
-            if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                    && playbackState.getState() == PlaybackState.STATE_PLAYING) {
-                return controller;
-            }
-        }
-        return null;
+        return MediaOutputUtils.getActiveLocalMediaController(mContext.getSystemService(
+                MediaSessionManager.class));
     }
 
     @Override
diff --git a/src/com/android/settings/media/MediaOutputUtils.java b/src/com/android/settings/media/MediaOutputUtils.java
new file mode 100644
index 0000000..b31d21f
--- /dev/null
+++ b/src/com/android/settings/media/MediaOutputUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.media;
+
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.sound.MediaOutputPreferenceController;
+
+/**
+ * Utilities that can be shared between {@link MediaOutputIndicatorWorker} and
+ * {@link MediaOutputPreferenceController}.
+ */
+public class MediaOutputUtils {
+
+    private static final String TAG = "MediaOutputUtils";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     *  Returns a {@link MediaController} that state is playing and type is local playback,
+     *  and also have active sessions.
+     */
+    @Nullable
+    public static MediaController getActiveLocalMediaController(
+            MediaSessionManager mediaSessionManager) {
+
+        MediaController localController = null;
+        for (MediaController controller : mediaSessionManager.getActiveSessions(null)) {
+            final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
+            if (pi == null) {
+                // do nothing
+                continue;
+            }
+            final PlaybackState playbackState = controller.getPlaybackState();
+            if (playbackState == null) {
+                // do nothing
+                continue;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "getActiveLocalMediaController() package name : "
+                        + controller.getPackageName()
+                        + ", play back type : " + pi.getPlaybackType() + ", play back state : "
+                        + playbackState.getState());
+            }
+            if (playbackState.getState() != PlaybackState.STATE_PLAYING) {
+                // do nothing
+                continue;
+            }
+            if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                if (localController != null && TextUtils.equals(localController.getPackageName(),
+                        controller.getPackageName())) {
+                    localController = null;
+                }
+                continue;
+            }
+            if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+                if (localController == null) {
+                    localController = controller;
+                }
+            }
+        }
+        return localController;
+    }
+}
diff --git a/src/com/android/settings/sound/MediaOutputPreferenceController.java b/src/com/android/settings/sound/MediaOutputPreferenceController.java
index da92b2b..21d90d8 100644
--- a/src/com/android/settings/sound/MediaOutputPreferenceController.java
+++ b/src/com/android/settings/sound/MediaOutputPreferenceController.java
@@ -22,14 +22,13 @@
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
 import android.text.TextUtils;
 
-import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
+import com.android.settings.media.MediaOutputUtils;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.A2dpProfile;
 import com.android.settingslib.bluetooth.HearingAidProfile;
@@ -51,7 +50,8 @@
 
     public MediaOutputPreferenceController(Context context, String key) {
         super(context, key);
-        mMediaController = getActiveLocalMediaController();
+        mMediaController = MediaOutputUtils.getActiveLocalMediaController(context.getSystemService(
+                MediaSessionManager.class));
     }
 
     @Override
@@ -141,26 +141,4 @@
         }
         return false;
     }
-
-    @Nullable
-    MediaController getActiveLocalMediaController() {
-        final MediaSessionManager mMediaSessionManager = mContext.getSystemService(
-                MediaSessionManager.class);
-
-        for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
-            final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
-            if (pi == null) {
-                return null;
-            }
-            final PlaybackState playbackState = controller.getPlaybackState();
-            if (playbackState == null) {
-                return null;
-            }
-            if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
-                    && playbackState.getState() == PlaybackState.STATE_PLAYING) {
-                return controller;
-            }
-        }
-        return null;
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
index d9b2247..1970f6c 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
@@ -261,4 +261,29 @@
 
         assertThat(mMediaOutputIndicatorWorker.getActiveLocalMediaController()).isNull();
     }
+
+    @Test
+    public void getActiveLocalMediaController_bothHaveRemoteMediaAndLocalMedia_returnNull() {
+        final MediaController.PlaybackInfo playbackInfo = new MediaController.PlaybackInfo(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
+                100,
+                10,
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
+                null);
+        final PlaybackState playbackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1)
+                .build();
+        final MediaController remoteMediaController = mock(MediaController.class);
+
+        mMediaControllers.add(remoteMediaController);
+        initPlayback();
+
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        when(remoteMediaController.getPlaybackInfo()).thenReturn(playbackInfo);
+        when(remoteMediaController.getPlaybackState()).thenReturn(playbackState);
+
+        assertThat(mMediaOutputIndicatorWorker.getActiveLocalMediaController()).isNull();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputUtilsTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputUtilsTest.java
new file mode 100644
index 0000000..2daf207
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputUtilsTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class MediaOutputUtilsTest {
+
+    @Mock
+    private MediaSessionManager mMediaSessionManager;
+    @Mock
+    private MediaController mMediaController;
+
+    private Context mContext;
+    private List<MediaController> mMediaControllers = new ArrayList<>();
+    private PlaybackState mPlaybackState;
+    private MediaController.PlaybackInfo mPlaybackInfo;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        doReturn(mMediaSessionManager).when(mContext).getSystemService(MediaSessionManager.class);
+        mMediaControllers.add(mMediaController);
+        doReturn(mMediaControllers).when(mMediaSessionManager).getActiveSessions(any());
+    }
+
+    @Test
+    public void getActiveLocalMediaController_localMediaPlaying_returnController() {
+        initPlayback();
+
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+
+        assertThat(MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager)).isEqualTo(
+                mMediaController);
+    }
+
+    @Test
+    public void getActiveLocalMediaController_remoteMediaPlaying_returnNull() {
+        mPlaybackInfo = new MediaController.PlaybackInfo(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
+                100,
+                10,
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
+                null);
+        mPlaybackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1)
+                .build();
+
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+
+        assertThat(MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager)).isNull();
+    }
+
+    @Test
+    public void getActiveLocalMediaController_localMediaStopped_returnNull() {
+        mPlaybackInfo = new MediaController.PlaybackInfo(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
+                100,
+                10,
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
+                null);
+        mPlaybackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_STOPPED, 0, 1)
+                .build();
+
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+
+        assertThat(MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager)).isNull();
+    }
+
+    @Test
+    public void getActiveLocalMediaController_bothHaveRemoteMediaAndLocalMedia_returnNull() {
+        final MediaController.PlaybackInfo playbackInfo = new MediaController.PlaybackInfo(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
+                100,
+                10,
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
+                null);
+        final PlaybackState playbackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1)
+                .build();
+        final MediaController remoteMediaController = mock(MediaController.class);
+
+        mMediaControllers.add(remoteMediaController);
+        initPlayback();
+
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        when(remoteMediaController.getPlaybackInfo()).thenReturn(playbackInfo);
+        when(remoteMediaController.getPlaybackState()).thenReturn(playbackState);
+
+        assertThat(MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager)).isNull();
+    }
+
+    private void initPlayback() {
+        mPlaybackInfo = new MediaController.PlaybackInfo(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
+                100,
+                10,
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
+                null);
+        mPlaybackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1)
+                .build();
+    }
+}