Monitor AudioManger Playback state to update the AVRCP playback state

Some carkit won't play A2DP audio when AVRCP playback state is paused.
Causing navigation sound not able to heard from carkit speaker if there
is not music playing.
Add registration for onPlaybackConfigChanged can helps to monitor if
there is any audio playback is active, So AVRCP can report correct
playback state to remote to play the audio playback.
Some additonal rules when updating playback state:
- Do not update audio playback state when there is media player playing.
This is to prevent send playback state paused when audio playback and
media player are playing at same time and audio playback stop first.
- Update playback status of acitve media player when audio playbac stop playing
and there is not media player playing.

Bug: 128982202
Bug: 138249637
Test: atest BluetoothInstrumentationTests
Merged-In: I8f28a7eb0c3f923c656c889375c660b38d9e6236
Change-Id: I8f28a7eb0c3f923c656c889375c660b38d9e6236
diff --git a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index b5886c2..3ce2c5d 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -23,6 +23,9 @@
 import android.os.Message;
 import android.util.Log;
 
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -47,6 +50,7 @@
     private static final int MSG_CONNECT_CB = 1;
     private static final int MSG_TIMEOUT = 2;
 
+    private static BrowsablePlayerConnector sInjectConnector;
     private Handler mHandler;
     private Context mContext;
     private PlayerListCallback mCallback;
@@ -58,11 +62,19 @@
         void run(List<BrowsedPlayerWrapper> result);
     }
 
+    private static void setInstanceForTesting(BrowsablePlayerConnector connector) {
+        Utils.enforceInstrumentationTestMode();
+        sInjectConnector = connector;
+    }
+
     static BrowsablePlayerConnector connectToPlayers(
             Context context,
             Looper looper,
             List<ResolveInfo> players,
             PlayerListCallback cb) {
+        if (sInjectConnector != null) {
+            return sInjectConnector;
+        }
         if (cb == null) {
             Log.wtfStack(TAG, "Null callback passed");
             return null;
diff --git a/src/com/android/bluetooth/avrcp/GPMWrapper.java b/src/com/android/bluetooth/avrcp/GPMWrapper.java
index 87e5ec0..ea9875d 100644
--- a/src/com/android/bluetooth/avrcp/GPMWrapper.java
+++ b/src/com/android/bluetooth/avrcp/GPMWrapper.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.avrcp;
 
 import android.media.session.MediaSession;
+import android.os.Looper;
 import android.util.Log;
 
 /**
@@ -28,6 +29,10 @@
     private static final String TAG = "AvrcpGPMWrapper";
     private static final boolean DEBUG = true;
 
+    GPMWrapper(MediaController controller, Looper looper) {
+        super(controller, looper);
+    }
+
     @Override
     boolean isMetadataSynced() {
         if (getQueue() == null) {
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 29f4cf9..1edbb14 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -24,6 +24,8 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
@@ -33,6 +35,7 @@
 import android.view.KeyEvent;
 
 import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -79,6 +82,8 @@
     private Looper mLooper; // Thread all media player callbacks and timeouts happen on
     private PackageManager mPackageManager;
     private MediaSessionManager mMediaSessionManager;
+    private MediaData mCurrMediaData = null;
+    private final AudioManager mAudioManager;
 
     private Map<Integer, MediaPlayerWrapper> mMediaPlayers =
             Collections.synchronizedMap(new HashMap<Integer, MediaPlayerWrapper>());
@@ -88,6 +93,9 @@
             Collections.synchronizedMap(new HashMap<Integer, BrowsedPlayerWrapper>());
     private int mActivePlayerId = NO_ACTIVE_PLAYER;
 
+    @VisibleForTesting
+    private boolean mAudioPlaybackIsActive = false;
+
     private AvrcpTargetService.ListCallback mCallback;
     private BrowsablePlayerConnector mBrowsablePlayerConnector;
 
@@ -122,6 +130,9 @@
         pkgFilter.addDataScheme(PACKAGE_SCHEME);
         context.registerReceiver(mPackageChangedBroadcastReceiver, pkgFilter);
 
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mAudioManager.registerAudioPlaybackCallback(mAudioPlaybackCallback, new Handler(mLooper));
+
         mMediaSessionManager =
                 (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
         mMediaSessionManager.addOnActiveSessionsChangedListener(
@@ -190,6 +201,8 @@
         mMediaSessionManager.setCallback(null, null);
         mMediaSessionManager = null;
 
+        mAudioManager.unregisterAudioPlaybackCallback(mAudioPlaybackCallback);
+
         mMediaPlayerIds.clear();
 
         for (MediaPlayerWrapper player : mMediaPlayers.values()) {
@@ -275,7 +288,16 @@
         final MediaPlayerWrapper player = getActivePlayer();
         if (player == null) return null;
 
-        return player.getPlaybackState();
+        PlaybackState state = player.getPlaybackState();
+        if (mAudioPlaybackIsActive
+                && (state == null || state.getState() != PlaybackState.STATE_PLAYING)) {
+            return new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING,
+                          state == null ? 0 : state.getPosition(),
+                          1.0f)
+                .build();
+        }
+        return state;
     }
 
     @NonNull
@@ -406,12 +428,8 @@
         }
     }
 
-    // Adds the controller to the MediaPlayerList or updates the controller if we already had
-    // a controller for a package. Returns the new ID of the controller where its added or its
-    // previous value if it already existed. Returns -1 if the controller passed in is invalid
-    int addMediaPlayer(android.media.session.MediaController controller) {
-        if (controller == null) return -1;
-
+    @VisibleForTesting
+    int addMediaPlayer(MediaController controller) {
         // Each new player has an ID of 1 plus the highest ID. The ID 0 is reserved to signify that
         // there is no active player. If we already have a browsable player for the package, reuse
         // that key.
@@ -427,7 +445,7 @@
         if (mMediaPlayers.containsKey(playerId)) {
             d("Already have a controller for the player: " + packageName + ", updating instead");
             MediaPlayerWrapper player = mMediaPlayers.get(playerId);
-            player.updateMediaController(MediaControllerFactory.wrap(controller));
+            player.updateMediaController(controller);
 
             // If the media controller we updated was the active player check if the media updated
             if (playerId == mActivePlayerId) {
@@ -437,8 +455,8 @@
             return playerId;
         }
 
-        MediaPlayerWrapper newPlayer = MediaPlayerWrapper.wrap(
-                MediaControllerFactory.wrap(controller),
+        MediaPlayerWrapper newPlayer = MediaPlayerWrapperFactory.wrap(
+                controller,
                 mLooper);
 
         Log.i(TAG, "Adding wrapped media player: " + packageName + " at key: "
@@ -448,6 +466,18 @@
         return playerId;
     }
 
+    // Adds the controller to the MediaPlayerList or updates the controller if we already had
+    // a controller for a package. Returns the new ID of the controller where its added or its
+    // previous value if it already existed. Returns -1 if the controller passed in is invalid
+    int addMediaPlayer(android.media.session.MediaController controller) {
+        if (controller == null) {
+            e("Trying to add a null MediaController");
+            return -1;
+        }
+
+        return addMediaPlayer(MediaControllerFactory.wrap(controller));
+    }
+
     void removeMediaPlayer(int playerId) {
         if (!mMediaPlayers.containsKey(playerId)) {
             e("Trying to remove nonexistent media player: " + playerId);
@@ -495,7 +525,12 @@
             sendFolderUpdate(true, true, false);
         }
 
-        sendMediaUpdate(getActivePlayer().getCurrentMediaData());
+        MediaData data = getActivePlayer().getCurrentMediaData();
+        if (mAudioPlaybackIsActive) {
+            data.state = mCurrMediaData.state;
+            Log.d(TAG, "setActivePlayer mAudioPlaybackIsActive=true, state=" + data.state);
+        }
+        sendMediaUpdate(data);
     }
 
     // TODO (apanicke): Add logging for media key events in dumpsys
@@ -528,6 +563,8 @@
             data.queue.add(data.metadata);
         }
 
+        Log.d(TAG, "sendMediaUpdate state=" + data.state);
+        mCurrMediaData = data;
         mCallback.run(data);
     }
 
@@ -591,6 +628,75 @@
         }
     };
 
+    void updateMediaForAudioPlayback() {
+        MediaData currMediaData = null;
+        PlaybackState currState = null;
+        if (getActivePlayer() == null) {
+            Log.d(TAG, "updateMediaForAudioPlayback: no active player");
+            PlaybackState.Builder builder = new PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_STOPPED, 0L, 0L);
+            List<Metadata> queue = new ArrayList<Metadata>();
+            queue.add(Util.empty_data());
+            currMediaData = new MediaData(
+                    Util.empty_data(),
+                    builder.build(),
+                    queue
+                );
+        } else {
+            currMediaData = getActivePlayer().getCurrentMediaData();
+            currState = currMediaData.state;
+        }
+
+        if (currState != null
+                && currState.getState() == PlaybackState.STATE_PLAYING) {
+            Log.i(TAG, "updateMediaForAudioPlayback: Active player is playing, drop it");
+            return;
+        }
+
+        if (mAudioPlaybackIsActive) {
+            PlaybackState.Builder builder = new PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_PLAYING,
+                        currState == null ? 0 : currState.getPosition(),
+                        1.0f);
+            currMediaData.state = builder.build();
+        }
+        Log.i(TAG, "updateMediaForAudioPlayback: update state=" + currMediaData.state);
+        sendMediaUpdate(currMediaData);
+    }
+
+    @VisibleForTesting
+    void injectAudioPlaybacActive(boolean isActive) {
+        mAudioPlaybackIsActive = isActive;
+        updateMediaForAudioPlayback();
+    }
+
+    private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback =
+            new AudioManager.AudioPlaybackCallback() {
+        @Override
+        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+            if (configs == null) {
+                return;
+            }
+            boolean isActive = false;
+            Log.v(TAG, "onPlaybackConfigChanged(): Configs list size=" + configs.size());
+            for (AudioPlaybackConfiguration config : configs) {
+                if (config.isActive()) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onPlaybackConfigChanged(): config="
+                                 + AudioPlaybackConfiguration.toLogFriendlyString(config));
+                    }
+                    isActive = true;
+                }
+            }
+            if (isActive != mAudioPlaybackIsActive) {
+                Log.d(TAG, "onPlaybackConfigChanged isActive=" + isActive
+                        + ", mAudioPlaybackIsActive=" + mAudioPlaybackIsActive);
+                mAudioPlaybackIsActive = isActive;
+                updateMediaForAudioPlayback();
+            }
+        }
+    };
+
     private final MediaPlayerWrapper.Callback mMediaPlayerCallback =
             new MediaPlayerWrapper.Callback() {
         @Override
@@ -605,6 +711,10 @@
                 return;
             }
 
+            if (mAudioPlaybackIsActive && (data.state.getState() != PlaybackState.STATE_PLAYING)) {
+                Log.d(TAG, "Some audio playbacks are still active, drop it");
+                return;
+            }
             sendMediaUpdate(data);
         }
     };
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index e4176b3..9eba036 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
@@ -54,10 +54,6 @@
     private final Object mCallbackLock = new Object();
     private Callback mRegisteredCallback = null;
 
-    protected MediaPlayerWrapper() {
-        mCurrentData = new MediaData(null, null, null);
-    }
-
     public interface Callback {
         void mediaUpdatedCallback(MediaData data);
     }
@@ -80,30 +76,15 @@
         return true;
     }
 
-    // TODO (apanicke): Implement a factory to make testing and creating interop wrappers easier
-    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
-        if (controller == null || looper == null) {
-            e("MediaPlayerWrapper.wrap(): Null parameter - Controller: " + controller
-                    + " | Looper: " + looper);
-            return null;
-        }
+    MediaPlayerWrapper(MediaController controller, Looper looper) {
+        mMediaController = controller;
+        mPackageName = controller.getPackageName();
+        mLooper = looper;
 
-        MediaPlayerWrapper newWrapper;
-        if (controller.getPackageName().equals("com.google.android.music")) {
-            Log.v(TAG, "Creating compatibility wrapper for Google Play Music");
-            newWrapper = new GPMWrapper();
-        } else {
-            newWrapper = new MediaPlayerWrapper();
-        }
-
-        newWrapper.mMediaController = controller;
-        newWrapper.mPackageName = controller.getPackageName();
-        newWrapper.mLooper = looper;
-
-        newWrapper.mCurrentData.queue = Util.toMetadataList(newWrapper.getQueue());
-        newWrapper.mCurrentData.metadata = Util.toMetadata(newWrapper.getMetadata());
-        newWrapper.mCurrentData.state = newWrapper.getPlaybackState();
-        return newWrapper;
+        mCurrentData = new MediaData(null, null, null);
+        mCurrentData.queue = Util.toMetadataList(getQueue());
+        mCurrentData.metadata = Util.toMetadata(getMetadata());
+        mCurrentData.state = getPlaybackState();
     }
 
     void cleanup() {
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java b/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java
new file mode 100644
index 0000000..d3ef751
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 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.bluetooth.avrcp;
+
+import android.os.Looper;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provide a method to inject custom MediaControllerWrapper objects for testing. By using the
+ * factory methods instead of calling the wrap method of MediaControllerWrapper directly, we can
+ * inject a custom MediaControllerWrapper that can be used with JUnit and Mockito to set
+ * expectations and validate behaviour in tests.
+ */
+public final class MediaPlayerWrapperFactory {
+    private static MediaPlayerWrapper sInjectedWrapper;
+
+    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
+        if (sInjectedWrapper != null) return sInjectedWrapper;
+        if (controller == null || looper == null) {
+            return null;
+        }
+
+        MediaPlayerWrapper newWrapper;
+        if (controller.getPackageName().equals("com.google.android.music")) {
+            newWrapper = new GPMWrapper(controller, looper);
+        } else {
+            newWrapper = new MediaPlayerWrapper(controller, looper);
+        }
+        return newWrapper;
+    }
+
+    @VisibleForTesting
+    static void inject(MediaPlayerWrapper wrapper) {
+        sInjectedWrapper = wrapper;
+    }
+}
+
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java
new file mode 100644
index 0000000..39c8b4c
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2019 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.bluetooth.avrcp;
+
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Looper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerListTest {
+    private MediaPlayerList mMediaPlayerList;
+
+    private @Captor ArgumentCaptor<AudioManager.AudioPlaybackCallback> mAudioCb;
+    private @Captor ArgumentCaptor<MediaPlayerWrapper.Callback> mPlayerWrapperCb;
+    private @Captor ArgumentCaptor<MediaData> mMediaUpdateData;
+    private @Mock Context mMockContext;
+    private @Mock AvrcpTargetService.ListCallback mAvrcpCallback;
+    private @Mock MediaController mMockController;
+    private @Mock MediaPlayerWrapper mMockPlayerWrapper;
+
+    private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false");
+    private MediaPlayerWrapper.Callback mActivePlayerCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        if (!mFlagDexmarker.equals("true")) {
+            System.setProperty("dexmaker.share_classloader", "true");
+        }
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        MockitoAnnotations.initMocks(this);
+
+        AudioManager mockAudioManager = mock(AudioManager.class);
+        when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
+
+        MediaSessionManager mMediaSessionManager =
+                (MediaSessionManager) InstrumentationRegistry.getTargetContext()
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        PackageManager mockPackageManager = mock(PackageManager.class);
+        when(mMockContext.getSystemService(Context.MEDIA_SESSION_SERVICE))
+            .thenReturn(mMediaSessionManager);
+
+        mMediaPlayerList =
+            new MediaPlayerList(Looper.myLooper(), InstrumentationRegistry.getTargetContext());
+
+        when(mMockContext.registerReceiver(any(), any())).thenReturn(null);
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mockPackageManager);
+        List<ResolveInfo> playerList = new ArrayList<ResolveInfo>();
+        when(mockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(null);
+
+        Method method = BrowsablePlayerConnector.class.getDeclaredMethod("setInstanceForTesting",
+                BrowsablePlayerConnector.class);
+        BrowsablePlayerConnector mockConnector = mock(BrowsablePlayerConnector.class);
+        method.setAccessible(true);
+        method.invoke(null, mockConnector);
+        mMediaPlayerList.init(mAvrcpCallback);
+
+        MediaControllerFactory.inject(mMockController);
+        MediaPlayerWrapperFactory.inject(mMockPlayerWrapper);
+
+        doReturn("testPlayer").when(mMockController).getPackageName();
+        when(mMockPlayerWrapper.isMetadataSynced()).thenReturn(false);
+        mMediaPlayerList.setActivePlayer(mMediaPlayerList.addMediaPlayer(mMockController));
+
+        verify(mMockPlayerWrapper).registerCallback(mPlayerWrapperCb.capture());
+        mActivePlayerCallback = mPlayerWrapperCb.getValue();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Method method = BrowsablePlayerConnector.class.getDeclaredMethod("setInstanceForTesting",
+                BrowsablePlayerConnector.class);
+        method.setAccessible(true);
+        method.invoke(null, (BrowsablePlayerConnector) null);
+
+        MediaControllerFactory.inject(null);
+        MediaPlayerWrapperFactory.inject(null);
+        mMediaPlayerList.cleanup();
+        if (!mFlagDexmarker.equals("true")) {
+            System.setProperty("dexmaker.share_classloader", mFlagDexmarker);
+        }
+    }
+
+    private MediaData prepareMediaData(int playbackState) {
+        PlaybackState.Builder builder = new PlaybackState.Builder();
+        builder.setState(playbackState, 0, 1);
+        ArrayList<Metadata> list = new ArrayList<Metadata>();
+        list.add(Util.empty_data());
+        MediaData newData = new MediaData(
+                Util.empty_data(),
+                builder.build(),
+                list);
+
+        return newData;
+    }
+
+    @Test
+    public void testUpdateMeidaDataForAudioPlaybackWhenActivePlayNotPlaying() {
+        // Verify update media data with playing state
+        doReturn(prepareMediaData(PlaybackState.STATE_PAUSED))
+            .when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(true);
+        verify(mAvrcpCallback).run(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(data.state.getState(), PlaybackState.STATE_PLAYING);
+
+        // verify update media data with current media player media data
+        MediaData currentMediaData = prepareMediaData(PlaybackState.STATE_PAUSED);
+        doReturn(currentMediaData).when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(false);
+        verify(mAvrcpCallback, times(2)).run(mMediaUpdateData.capture());
+        data = mMediaUpdateData.getValue();
+        Assert.assertEquals(data.metadata, currentMediaData.metadata);
+        Assert.assertEquals(data.state.toString(), currentMediaData.state.toString());
+        Assert.assertEquals(data.queue, currentMediaData.queue);
+    }
+
+    @Test
+    public void testUpdateMediaDataForActivePlayerWhenAudioPlaybackIsNotActive() {
+        MediaData currMediaData = prepareMediaData(PlaybackState.STATE_PLAYING);
+        mActivePlayerCallback.mediaUpdatedCallback(currMediaData);
+        verify(mAvrcpCallback).run(currMediaData);
+
+        currMediaData = prepareMediaData(PlaybackState.STATE_PAUSED);
+        mActivePlayerCallback.mediaUpdatedCallback(currMediaData);
+        verify(mAvrcpCallback).run(currMediaData);
+    }
+
+    @Test
+    public void testNotUpdateMediaDataForAudioPlaybackWhenActivePlayerIsPlaying() {
+        // Verify not update media data for Audio Playback when active player is playing
+        doReturn(prepareMediaData(PlaybackState.STATE_PLAYING))
+            .when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(true);
+        mMediaPlayerList.injectAudioPlaybacActive(false);
+        verify(mAvrcpCallback, never()).run(any());
+    }
+
+    @Test
+    public void testNotUpdateMediaDataForActivePlayerWhenAudioPlaybackIsActive() {
+        doReturn(prepareMediaData(PlaybackState.STATE_PLAYING))
+            .when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(true);
+        verify(mAvrcpCallback, never()).run(any());
+
+        // Verify not update active player media data when audio playback is active
+        mActivePlayerCallback.mediaUpdatedCallback(prepareMediaData(PlaybackState.STATE_PAUSED));
+        verify(mAvrcpCallback, never()).run(any());
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
index ee41320..1e41033 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
@@ -138,10 +138,10 @@
      */
     @Test
     public void testNullControllerLooper() {
-        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(null, mThread.getLooper());
+        MediaPlayerWrapper wrapper = MediaPlayerWrapperFactory.wrap(null, mThread.getLooper());
         Assert.assertNull(wrapper);
 
-        wrapper = MediaPlayerWrapper.wrap(mMockController, null);
+        wrapper = MediaPlayerWrapperFactory.wrap(mMockController, null);
         Assert.assertNull(wrapper);
     }
 
@@ -151,7 +151,8 @@
      */
     @Test
     public void testIsReady() {
-        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         Assert.assertTrue(wrapper.isPlaybackStateReady());
         Assert.assertTrue(wrapper.isMetadataReady());
 
@@ -179,7 +180,8 @@
     @Test
     public void testControllerUpdate() {
         // Create the wrapper object and register the looper with the timeout handler
-        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         Assert.assertTrue(wrapper.isPlaybackStateReady());
         Assert.assertTrue(wrapper.isMetadataReady());
         wrapper.registerCallback(mTestCbs);
@@ -213,7 +215,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -275,7 +277,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -319,7 +321,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -348,7 +350,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -375,7 +377,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Call getCurrentQueue() multiple times.
@@ -398,7 +400,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -455,7 +457,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Cleanup the wrapper
@@ -475,7 +477,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -505,7 +507,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -565,7 +567,7 @@
                 InstrumentationRegistry.getInstrumentation()
                         .acquireLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -615,7 +617,7 @@
                 InstrumentationRegistry.getInstrumentation()
                         .acquireLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller