Improve AVRCP quality and state handling (1/2)

Bug: 64749777
Test: Play music with various car kits and see that metadata shows up
      See go/oc-avrcp-sotu - 8/15/20 OC + Patches for further info
Change-Id: Ia9dd450860f5bc54bd249322bfb9762c6235dc32
(cherry picked from commit 6b95520b8fe8a5bd8bc264e06bec51f3591d2507)
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 8c94027..0835aef 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -48,20 +48,21 @@
     static private final long SINGLE_QID = 1;
     static private final String UNKNOWN_TITLE = "(unknown)";
 
+    static private final String GPM_BUNDLE_METADATA_KEY =
+            "com.google.android.music.mediasession.music_metadata";
+
     private AvrcpMediaRspInterface mMediaInterface;
     private @NonNull List<MediaSession.QueueItem> mNowPlayingList;
 
     private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
 
     private long mLastTrackIdSent;
-    private boolean mNowPlayingListUpdated;
 
     public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
         mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>();
         mNowPlayingList = mEmptyNowPlayingList;
         mMediaInterface = mediaInterface;
         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
-        mNowPlayingListUpdated = false;
     }
 
     void cleanup() {
@@ -69,7 +70,6 @@
         mNowPlayingList = mEmptyNowPlayingList;
         mMediaInterface = null;
         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
-        mNowPlayingListUpdated = false;
     }
 
     /* get now playing list from addressed player */
@@ -81,7 +81,7 @@
             mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
             return;
         }
-        List<MediaSession.QueueItem> items = getNowPlayingList(mediaController);
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
         getFolderItemsFilterAttr(bdaddr, reqObj, items, AvrcpConstants.BTRC_SCOPE_NOW_PLAYING,
                 reqObj.mStartItem, reqObj.mEndItem, mediaController);
     }
@@ -91,7 +91,7 @@
             @Nullable MediaController mediaController) {
         int status = AvrcpConstants.RSP_NO_ERROR;
         long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong();
-        List<MediaSession.QueueItem> items = getNowPlayingList(mediaController);
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
 
         // NOTE: this is out-of-spec (AVRCP 1.6.1 sec 6.10.4.3, p90) but we answer it anyway
         // because some CTs ask for it.
@@ -118,14 +118,10 @@
 
     /* Refresh and get the queue of now playing.
      */
-    private @NonNull List<MediaSession.QueueItem> getNowPlayingList(
-            @Nullable MediaController mediaController) {
+    @NonNull
+    List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
         if (mediaController == null) return mEmptyNowPlayingList;
         List<MediaSession.QueueItem> items = mediaController.getQueue();
-        if (items != null && !mNowPlayingListUpdated) {
-            mNowPlayingList = items;
-            return mNowPlayingList;
-        }
         if (items == null) {
             Log.i(TAG, "null queue from " + mediaController.getPackageName()
                             + ", constructing single-item list");
@@ -137,18 +133,17 @@
             items.add(current);
         }
 
+        if (!items.equals(mNowPlayingList)) sendNowPlayingListChanged();
         mNowPlayingList = items;
 
-        if (mNowPlayingListUpdated) sendNowPlayingListChanged();
-
         return mNowPlayingList;
     }
 
     private void sendNowPlayingListChanged() {
         if (mMediaInterface == null) return;
+        if (DEBUG) Log.d(TAG, "sendNowPlayingListChanged()");
         mMediaInterface.uidsChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
         mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
-        mNowPlayingListUpdated = false;
     }
 
     /* Constructs a queue item representing the current playing metadata from an
@@ -186,6 +181,7 @@
 
     private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) {
         if (metadata == null) {
+            Log.i(TAG, "fillBundle: metadata is null");
             return currentExtras;
         }
 
@@ -207,15 +203,10 @@
         return bundle;
     }
 
-    void updateNowPlayingList(@Nullable MediaController mediaController) {
-        mNowPlayingListUpdated = true;
-        getNowPlayingList(mediaController);
-    }
-
     /* Instructs media player to play particular media item */
     void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
         long qid = ByteBuffer.wrap(uid).getLong();
-        List<MediaSession.QueueItem> items = getNowPlayingList(mediaController);
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
 
         if (mediaController == null) {
             Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
@@ -246,7 +237,7 @@
     }
 
     void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
-        List<MediaSession.QueueItem> items = getNowPlayingList(mediaController);
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
         if (DEBUG) Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
         mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
     }
@@ -256,7 +247,6 @@
         long qid = getActiveQueueItemId(mediaController);
         byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
         // The nowPlayingList changed: the new list has the full data for the current item
-        if (type == AvrcpConstants.NOTIFICATION_TYPE_CHANGED) sendNowPlayingListChanged();
         mMediaInterface.trackChangedRsp(type, track);
         mLastTrackIdSent = qid;
     }
@@ -391,10 +381,22 @@
             MediaDescription desc = item.getDescription();
             Bundle extras = desc.getExtras();
             boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
+            MediaMetadata data = null;
             if (isCurrentTrack) {
                 if (DEBUG) Log.d(TAG, "getAttrValue: item is active, using current data");
-                extras = fillBundle(mediaController.getMetadata(), extras);
+                data = mediaController.getMetadata();
+                if (data == null)
+                    Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
             }
+
+            if (data == null) {
+                // TODO: This code can be removed when b/63117921 is resolved
+                data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY);
+                extras = null; // We no longer need the data in here
+            }
+
+            extras = fillBundle(data, extras);
+
             if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
             switch (attr) {
                 case AvrcpConstants.ATTRID_TITLE:
@@ -511,7 +513,9 @@
     private long getActiveQueueItemId(@Nullable MediaController controller) {
         if (controller == null) return MediaSession.QueueItem.UNKNOWN_ID;
         PlaybackState state = controller.getPlaybackState();
-        if (state == null) return MediaSession.QueueItem.UNKNOWN_ID;
+        if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
+                || state.getState() == PlaybackState.STATE_NONE)
+            return MediaSession.QueueItem.UNKNOWN_ID;
         long qid = state.getActiveQueueItemId();
         if (qid != MediaSession.QueueItem.UNKNOWN_ID) return qid;
         // Check if we're presenting a "one item queue"
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index 712b2a8..08adf81 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -57,9 +57,11 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -86,11 +88,12 @@
     private @NonNull PlaybackState mCurrentPlayState;
     private int mA2dpState;
     private int mPlayStatusChangedNT;
-    private int mReportedPlayStatus;
+    private byte mReportedPlayStatus;
     private int mTrackChangedNT;
     private int mPlayPosChangedNT;
     private int mAddrPlayerChangedNT;
     private int mReportedPlayerID;
+    private int mNowPlayingListChangedNT;
     private long mPlaybackIntervalMs;
     private long mLastReportedPosition;
     private long mNextPosMs;
@@ -99,7 +102,6 @@
     private int mRemoteVolume;
     private int mLastRemoteVolume;
     private int mInitialRemoteVolume;
-    private BrowsablePlayerListBuilder mBrowsableListBuilder;
 
     /* Local volume in audio index 0-15 */
     private int mLocalVolume;
@@ -162,10 +164,8 @@
     private static final int MSG_ABS_VOL_TIMEOUT = 17;
     private static final int MSG_SET_A2DP_AUDIO_STATE = 18;
     private static final int MSG_NOW_PLAYING_CHANGED_RSP = 19;
-    private static final int MSG_UPDATE_MEDIA = 20;
 
     private static final int CMD_TIMEOUT_DELAY = 2000;
-    private static final int MEDIA_DWELL_TIME = 1000;
     private static final int MAX_ERROR_RETRY_TIMES = 6;
     private static final int AVRCP_MAX_VOL = 127;
     private static final int AVRCP_BASE_VOLUME_STEP = 1;
@@ -244,6 +244,7 @@
         mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mPlaybackIntervalMs = 0L;
         mLastReportedPosition = -1;
         mNextPosMs = -1;
@@ -280,8 +281,6 @@
             mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
         }
 
-        mBrowsableListBuilder = new BrowsablePlayerListBuilder();
-
         // Register for package removal intent broadcasts for media button receiver persistence
         IntentFilter pkgFilter = new IntentFilter();
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -292,7 +291,7 @@
         context.registerReceiver(mAvrcpReceiver, pkgFilter);
 
         IntentFilter bootFilter = new IntentFilter();
-        bootFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
+        bootFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         context.registerReceiver(mBootReceiver, bootFilter);
     }
 
@@ -328,7 +327,7 @@
         if (manager == null || manager.isUserUnlocked()) {
             if (DEBUG) Log.d(TAG, "User already unlocked, initializing player lists");
             // initialize browsable player list and build media player list
-            mBrowsableListBuilder.start();
+            buildBrowsablePlayerList();
         }
     }
 
@@ -357,7 +356,6 @@
         mContext.unregisterReceiver(mAvrcpReceiver);
         mContext.unregisterReceiver(mBootReceiver);
 
-        mBrowsableListBuilder.cleanup();
         mAddressedMediaPlayer.cleanup();
         mAvrcpBrowseManager.cleanup();
     }
@@ -373,12 +371,13 @@
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
             if (DEBUG) Log.v(TAG, "onMetadataChanged");
-            scheduleMediaUpdate();
+            updateCurrentMediaState(false);
         }
         @Override
         public synchronized void onPlaybackStateChanged(PlaybackState state) {
             if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
-            scheduleMediaUpdate();
+
+            updateCurrentMediaState(false);
         }
 
         @Override
@@ -478,7 +477,7 @@
             case MSG_NOW_PLAYING_CHANGED_RSP:
                 if (DEBUG) Log.v(TAG, "MSG_NOW_PLAYING_CHANGED_RSP");
                 removeMessages(MSG_NOW_PLAYING_CHANGED_RSP);
-                mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
+                updateCurrentMediaState(false);
                 break;
 
             case MSG_PLAY_INTERVAL_TIMEOUT:
@@ -692,7 +691,7 @@
             case MSG_SET_A2DP_AUDIO_STATE:
                 if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
                 mA2dpState = msg.arg1;
-                scheduleMediaUpdate();
+                updateCurrentMediaState(false);
                 break;
 
             case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
@@ -778,13 +777,6 @@
                 handlePassthroughCmd(msg.arg1, msg.arg2);
                 break;
 
-            case MSG_UPDATE_MEDIA:
-                if (DEBUG) Log.v(TAG, "MSG_UPDATE_MEDIA");
-                // Throttle to once per MEDIA_DWELL_TIME
-                removeMessages(MSG_UPDATE_MEDIA);
-                updateCurrentMediaState(false);
-                break;
-
             default:
                 Log.e(TAG, "unknown message! msg.what=" + msg.what);
                 break;
@@ -834,10 +826,6 @@
 
         if (newState != null) mCurrentPlayState = newState;
 
-        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
-                && (mReportedPlayStatus != newPlayStatus)) {
-            sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
-        }
         return mCurrentPlayState;
     }
 
@@ -974,11 +962,6 @@
         }
     }
 
-    private void scheduleMediaUpdate() {
-        Message msg = mHandler.obtainMessage(MSG_UPDATE_MEDIA);
-        mHandler.sendMessageDelayed(msg, MEDIA_DWELL_TIME);
-    }
-
     private void updateCurrentMediaState(boolean registering) {
         // Only do player updates when we aren't registering for track changes.
         if (!registering) {
@@ -986,9 +969,13 @@
                 registerNotificationRspAvalPlayerChangedNative(
                         AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
                 mAvailablePlayerViewChanged = false;
+                return;
             }
             if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
                     && mReportedPlayerID != mCurrAddrPlayerID) {
+                registerNotificationRspAvalPlayerChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+                mAvailablePlayerViewChanged = false;
                 registerNotificationRspAddrPlayerChangedNative(
                         AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter);
                 mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
@@ -997,6 +984,8 @@
                 mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
                 mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
                 mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
                 // If the player changed, they need to re-request anything here again
                 // so we can skip the rest of the update.
                 return;
@@ -1010,25 +999,63 @@
             if (mMediaController == null) {
                 currentAttributes = new MediaAttributes(null);
             } else {
-                newState = mMediaController.getPlaybackState();
                 currentAttributes = new MediaAttributes(mMediaController.getMetadata());
             }
         }
 
-        long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
-        if (newState != null) newQueueId = newState.getActiveQueueItemId();
-        Log.v(TAG, "Media update: id " + mLastQueueId + "➡" + newQueueId + "? "
-                        + currentAttributes.toRedactedString());
-        // Notify track changed if:
-        //  - The CT is registering for the notification
-        //  - Queue ID is UNKNOWN and MediaMetadata is different
-        //  - Queue ID is valid and different and MediaMetadata is different
-        if (registering || (((newQueueId == -1) || (newQueueId != mLastQueueId))
-                                   && !currentAttributes.equals(mMediaAttributes))) {
-            sendTrackChangedRsp(registering);
-            mMediaAttributes = currentAttributes;
-            mLastQueueId = newQueueId;
+        byte newPlayStatus = getBluetoothPlayState(newState);
+
+        if (newState.getState() != PlaybackState.STATE_BUFFERING
+                && newState.getState() != PlaybackState.STATE_NONE) {
+            long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
+            if (newState != null) newQueueId = newState.getActiveQueueItemId();
+            Log.v(TAG, "Media update: id " + mLastQueueId + "➡" + newQueueId + "? "
+                            + currentAttributes.toRedactedString() + " : "
+                            + mMediaAttributes.toRedactedString());
+
+            // Dont send now playing list changed if the player doesn't support browsing
+            MediaPlayerInfo info = getAddressedPlayerInfo();
+            if (info != null && info.isBrowseSupported()) {
+                Log.v(TAG, "Check if NowPlayingList is updated");
+                mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
+            }
+
+            if ((newQueueId == -1 || newQueueId != mLastQueueId)
+                    && currentAttributes.equals(mMediaAttributes)
+                    && newPlayStatus == PLAYSTATUS_PLAYING
+                    && mReportedPlayStatus == PLAYSTATUS_STOPPED) {
+                // Most carkits like seeing the track changed before the
+                // playback state changed, but some controllers are slow
+                // to update their metadata. Hold of on sending the playback state
+                // update until after we know the current metadata is up to date
+                // and track changed has been sent. This was seen on BMW carkits
+                Log.i(TAG, "Waiting for metadata update to send track changed");
+
+                return;
+            }
+
+            // Notify track changed if:
+            //  - The CT is registering for the notification
+            //  - Queue ID is UNKNOWN and MediaMetadata is different
+            //  - Queue ID is valid and different and MediaMetadata is different
+            if (registering || ((newQueueId == -1 || newQueueId != mLastQueueId)
+                                       && !currentAttributes.equals(mMediaAttributes))) {
+                Log.v(TAG, "Send track changed");
+                mMediaAttributes = currentAttributes;
+                mLastQueueId = newQueueId;
+                sendTrackChangedRsp(registering);
+            }
+        } else {
+            Log.i(TAG, "Skipping update due to invalid playback state");
         }
+
+        // still send the updated play state if the playback state is none or buffering
+        Log.e(TAG, "play status change " + mReportedPlayStatus + "➡" + newPlayStatus);
+        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
+                && (mReportedPlayStatus != newPlayStatus)) {
+            sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
+        }
+
         sendPlayPosNotificationRsp(false);
     }
 
@@ -1063,14 +1090,13 @@
             case EVT_PLAY_STATUS_CHANGED:
                 mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
                 updatePlaybackState();
-                sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
-                        getBluetoothPlayState(mCurrentPlayState));
+                sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM, mReportedPlayStatus);
                 break;
 
             case EVT_TRACK_CHANGED:
                 Log.v(TAG, "Track changed notification enabled");
                 mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                updateCurrentMediaState(true);
+                sendTrackChangedRsp(true);
                 break;
 
             case EVT_PLAY_POS_CHANGED:
@@ -1105,6 +1131,7 @@
             case EVENT_NOW_PLAYING_CONTENT_CHANGED:
                 if (DEBUG) Log.d(TAG, "Now Playing List changed notification enabled");
                 /* send interim response to remote device */
+                mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
                 if (!registerNotificationRspNowPlayingChangedNative(
                         AvrcpConstants.NOTIFICATION_TYPE_INTERIM)) {
                     Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: " +
@@ -1160,8 +1187,7 @@
 
     private boolean isPlayingState(@Nullable PlaybackState state) {
         if (state == null) return false;
-        return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING)
-                || (state.getState() == PlaybackState.STATE_BUFFERING);
+        return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING);
     }
 
     /**
@@ -1390,10 +1416,10 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-                if (DEBUG) Log.d(TAG, "Boot completed, initializing player lists");
+            if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
+                if (DEBUG) Log.d(TAG, "User unlocked, initializing player lists");
                 /* initializing media player's list */
-                mBrowsableListBuilder.start();
+                buildBrowsablePlayerList();
             }
         }
     }
@@ -1439,7 +1465,7 @@
             // new package has been added.
             if (isBrowsableListUpdated(packageName)) {
                 // Rebuilding browsable players list
-                mBrowsableListBuilder.start();
+                buildBrowsablePlayerList();
             }
         }
     }
@@ -1548,8 +1574,15 @@
         // checking for error cases
         if (mMediaPlayerInfoList.isEmpty()) {
             status = AvrcpConstants.RSP_NO_AVBL_PLAY;
-            Log.w(TAG, " No Available Players to set, sending response back ");
+            Log.w(TAG, "setBrowsedPlayer: No available players! ");
         } else {
+            // Workaround for broken controllers selecting ID 0
+            // Seen at least on Ford, Chevrolet MyLink
+            if (selectedId == 0) {
+                Log.w(TAG, "setBrowsedPlayer: workaround invalid id 0");
+                selectedId = mCurrAddrPlayerID;
+            }
+
             // update current browse player id and start browsing service
             updateNewIds(mCurrAddrPlayerID, selectedId);
             String browsedPackage = getPackageName(selectedId);
@@ -1582,9 +1615,15 @@
                 @Override
                 public void onActiveSessionsChanged(
                         List<android.media.session.MediaController> newControllers) {
+                    Set<String> updatedPackages = new HashSet<String>();
                     // Update the current players
                     for (android.media.session.MediaController controller : newControllers) {
+                        String packageName = controller.getPackageName();
+                        if (DEBUG) Log.v(TAG, "ActiveSession: " + MediaController.wrap(controller));
+                        // Only use the first (highest priority) controller from each package
+                        if (updatedPackages.contains(packageName)) continue;
                         addMediaPlayerController(controller);
+                        updatedPackages.add(packageName);
                     }
 
                     if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
@@ -1592,7 +1631,7 @@
                             Log.v(TAG, "No addressed player but active sessions, taking first.");
                         setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
                     }
-                    scheduleMediaUpdate();
+                    updateCurrentMediaState(false);
                 }
             };
 
@@ -1602,13 +1641,17 @@
             updateCurrentController(0, mCurrBrowsePlayerID);
             return;
         }
+        if (packageName.equals("com.android.server.telecom")) {
+            Log.d(TAG, "Ignore addressed media session change to telecom");
+            return;
+        }
         // No change.
         if (getPackageName(mCurrAddrPlayerID).equals(packageName)) return;
         if (DEBUG) Log.v(TAG, "Changing addressed media session to " + packageName);
         // If the player doesn't exist, we need to add it.
         if (getMediaPlayerInfo(packageName) == null) {
             addMediaPlayerPackage(packageName);
-            scheduleMediaUpdate();
+            updateCurrentMediaState(false);
         }
         synchronized (mMediaPlayerInfoList) {
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
@@ -1616,7 +1659,7 @@
                     int newAddrID = entry.getKey();
                     if (DEBUG) Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
                     updateCurrentController(newAddrID, mCurrBrowsePlayerID);
-                    scheduleMediaUpdate();
+                    updateCurrentMediaState(false);
                     return;
                 }
             }
@@ -1628,6 +1671,10 @@
     private void setActiveMediaSession(MediaSession.Token token) {
         android.media.session.MediaController activeController =
                 new android.media.session.MediaController(mContext, token);
+        if (activeController.getPackageName().equals("com.android.server.telecom")) {
+            Log.d(TAG, "Ignore active media session change to telecom");
+            return;
+        }
         if (DEBUG) Log.v(TAG, "Set active media session " + activeController.getPackageName());
         addMediaPlayerController(activeController);
         setAddressedMediaSessionPackage(activeController.getPackageName());
@@ -1667,71 +1714,33 @@
         return browseServiceName;
     }
 
-    private class BrowsablePlayerListBuilder extends MediaBrowser.ConnectionCallback {
-        List<ResolveInfo> mWaiting;
-        BrowsePlayerInfo mCurrentPlayer;
-        MediaBrowser mCurrentBrowser;
-        boolean mPlayersChanged;
-
-        public BrowsablePlayerListBuilder() {}
-
-        public void start() {
+    void buildBrowsablePlayerList() {
+        synchronized (mBrowsePlayerInfoList) {
             mBrowsePlayerInfoList.clear();
-            cleanup();
             Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
-            mWaiting = mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
-            connectNextPlayer();
-        }
+            List<ResolveInfo> playerList =
+                    mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
 
-        public void cleanup() {
-            if (mWaiting != null) mWaiting.clear();
-            mPlayersChanged = false;
-            if (mCurrentBrowser != null) mCurrentBrowser.disconnect();
-        }
+            for (ResolveInfo info : playerList) {
+                String displayableName = info.loadLabel(mPackageManager).toString();
+                String serviceName = info.serviceInfo.name;
+                String packageName = info.serviceInfo.packageName;
 
-        private void connectNextPlayer() {
-            if (mWaiting.isEmpty()) {
-                // Done. Send players changed if needed.
-                if (mPlayersChanged) {
-                    registerNotificationRspAvalPlayerChangedNative(
-                            AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+                if (DEBUG) Log.d(TAG, "Adding " + serviceName + " to list of browsable players");
+                BrowsePlayerInfo currentPlayer =
+                        new BrowsePlayerInfo(packageName, displayableName, serviceName);
+                mBrowsePlayerInfoList.add(currentPlayer);
+                MediaPlayerInfo playerInfo = getMediaPlayerInfo(packageName);
+                MediaController controller =
+                        (playerInfo == null) ? null : playerInfo.getMediaController();
+                // Refresh the media player entry so it notices we can browse
+                if (controller != null) {
+                    addMediaPlayerController(controller.getWrappedInstance());
+                } else {
+                    addMediaPlayerPackage(packageName);
                 }
-                return;
             }
-            ResolveInfo info = mWaiting.remove(0);
-            String displayableName = info.loadLabel(mPackageManager).toString();
-            String serviceName = info.serviceInfo.name;
-            String packageName = info.serviceInfo.packageName;
-
-            mCurrentPlayer = new BrowsePlayerInfo(packageName, displayableName, serviceName);
-            mCurrentBrowser = new MediaBrowser(
-                    mContext, new ComponentName(packageName, serviceName), this, null);
-            if (DEBUG) Log.d(TAG, "Trying to connect to " + serviceName);
-            mCurrentBrowser.connect();
-        }
-
-        @Override
-        public void onConnected() {
-            Log.d(TAG, "BrowsablePlayerListBuilder: " + mCurrentPlayer.packageName + " OK");
-            mCurrentBrowser.disconnect();
-            mCurrentBrowser = null;
-            mBrowsePlayerInfoList.add(mCurrentPlayer);
-            MediaPlayerInfo info = getMediaPlayerInfo(mCurrentPlayer.packageName);
-            MediaController controller = (info == null) ? null : info.getMediaController();
-            // Refresh the media player entry so it notices we can browse
-            if (controller != null) {
-                addMediaPlayerController(controller.getWrappedInstance());
-            } else {
-                addMediaPlayerPackage(mCurrentPlayer.packageName);
-            }
-            mPlayersChanged = true;
-            connectNextPlayer();
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            Log.d(TAG, "BrowsablePlayerListBuilder: " + mCurrentPlayer.packageName + " FAIL");
-            connectNextPlayer();
+            updateCurrentMediaState(false);
         }
     }
 
@@ -1755,7 +1764,7 @@
                 addMediaPlayerController(controller);
             }
 
-            scheduleMediaUpdate();
+            updateCurrentMediaState(false);
 
             if (mMediaPlayerInfoList.size() > 0) {
                 // Set the first one as the Addressed Player
@@ -1804,6 +1813,10 @@
         int updateId = -1;
         boolean updated = false;
         boolean currentRemoved = false;
+        if (info.getPackageName().equals("com.android.server.telecom")) {
+            Log.d(TAG, "Skip adding telecom to the media player info list");
+            return updated;
+        }
         synchronized (mMediaPlayerInfoList) {
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
                 MediaPlayerInfo current = entry.getValue();
@@ -1828,11 +1841,10 @@
                 mAvailablePlayerViewChanged = true;
             }
             mMediaPlayerInfoList.put(updateId, info);
-            if (DEBUG)
-                Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
-            if (currentRemoved || updateId == mCurrAddrPlayerID) {
-                updateCurrentController(updateId, mCurrBrowsePlayerID);
-            }
+        }
+        if (DEBUG) Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
+        if (currentRemoved || updateId == mCurrAddrPlayerID) {
+            updateCurrentController(updateId, mCurrBrowsePlayerID);
         }
         return updated;
     }
@@ -1887,9 +1899,9 @@
 
         switch (pbState.getState()) {
             case PlaybackState.STATE_PLAYING:
-            case PlaybackState.STATE_BUFFERING:
                 return PLAYSTATUS_PLAYING;
 
+            case PlaybackState.STATE_BUFFERING:
             case PlaybackState.STATE_STOPPED:
             case PlaybackState.STATE_NONE:
             case PlaybackState.STATE_CONNECTING:
@@ -2041,14 +2053,18 @@
             short[] featureBitMaskValues =
                     new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
 
-            int players = 0;
+            // Reserve the first spot for the currently addressed player if
+            // we have one
+            int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+                int idx = players;
+                if (entry.getKey() == mCurrAddrPlayerID) idx = 0;
                 MediaPlayerInfo info = entry.getValue();
-                playerIds[players] = entry.getKey();
-                playerTypes[players] = info.getMajorType();
-                playerSubTypes[players] = info.getSubType();
-                displayableNameArray[players] = info.getDisplayableName();
-                playStatusValues[players] = info.getPlayStatus();
+                playerIds[idx] = entry.getKey();
+                playerTypes[idx] = info.getMajorType();
+                playerSubTypes[idx] = info.getSubType();
+                displayableNameArray[idx] = info.getDisplayableName();
+                playStatusValues[idx] = info.getPlayStatus();
 
                 short[] featureBits = info.getFeatureBitMask();
                 for (int numBit = 0; numBit < featureBits.length; numBit++) {
@@ -2056,19 +2072,18 @@
                     byte octet = (byte) (featureBits[numBit] / 8);
                     /* gives the bit position within the octet */
                     byte bit = (byte) (featureBits[numBit] % 8);
-                    featureBitMaskValues[(players * AvrcpConstants.AVRC_FEATURE_MASK_SIZE)
-                            + octet] |= (1 << bit);
+                    featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |=
+                            (1 << bit);
                 }
 
                 /* printLogs */
                 if (DEBUG) {
-                    Log.d(TAG, "Player " + playerIds[players] + ": " + displayableNameArray[players]
-                                    + " type: " + playerTypes[players] + ", "
-                                    + playerSubTypes[players] + " status: "
-                                    + playStatusValues[players]);
+                    Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
+                                    + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
+                                    + " status: " + playStatusValues[idx]);
                 }
 
-                players++;
+                if (idx != 0) players++;
             }
 
             if (DEBUG) Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
@@ -2125,14 +2140,12 @@
                 mMediaController = newController;
                 if (mMediaController != null) {
                     mMediaController.registerCallback(mMediaControllerCb, mHandler);
-                    mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
                 } else {
-                    mAddressedMediaPlayer.updateNowPlayingList(null);
                     registerRsp = false;
                 }
             }
         }
-        scheduleMediaUpdate();
+        updateCurrentMediaState(false);
         return registerRsp;
     }
 
@@ -2207,6 +2220,12 @@
     }
 
     private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
+        if (itemAttr.mUidCounter != sUIDCounter) {
+            Log.e(TAG, "handleGetItemAttr: invaild uid counter.");
+            getItemAttrRspNative(
+                    itemAttr.mAddress, AvrcpConstants.RSP_UID_CHANGED, (byte) 0, null, null);
+            return;
+        }
         if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
             if (mCurrAddrPlayerID == NO_PLAYER_ID) {
                 getItemAttrRspNative(
@@ -2503,9 +2522,15 @@
         }
 
         public void nowPlayingChangedRsp(int type) {
+            if (mNowPlayingListChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+                if (DEBUG) Log.d(TAG, "NowPlayingListChanged: Not registered or requesting.");
+                return;
+            }
+
             if (!registerNotificationRspNowPlayingChangedNative(type)) {
                 Log.e(TAG, "registerNotificationRspNowPlayingChangedNative failed!");
             }
+            mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         }
 
         public void trackChangedRsp(int type, byte[] uid) {
diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
index c430ea9..397e17a 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
@@ -53,6 +53,7 @@
 
     /*  package and service name of target Media Player which is set for browsing */
     private String mPackageName;
+    private String mConnectingPackageName;
     private String mClassName;
     private Context mContext;
     private AvrcpMediaRspInterface mMediaInterface;
@@ -83,20 +84,34 @@
     private List<MediaBrowser.MediaItem> mFolderItems = null;
 
     /* Connection state callback handler */
-    private MediaBrowser.ConnectionCallback browseMediaConnectionCallback =
-            new MediaBrowser.ConnectionCallback() {
+    class MediaConnectionCallback extends MediaBrowser.ConnectionCallback {
+        private String mCallbackPackageName;
+        private MediaBrowser mBrowser;
+
+        public MediaConnectionCallback(String packageName) {
+            this.mCallbackPackageName = packageName;
+        }
+
+        public void setBrowser(MediaBrowser b) {
+            mBrowser = b;
+        }
 
         @Override
         public void onConnected() {
             mConnState = CONNECTED;
             if (DEBUG) Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
             /* perform init tasks and set player as browsed player on successful connection */
-            onBrowseConnect();
+            onBrowseConnect(mCallbackPackageName, mBrowser);
+
+            // Remove what could be a circular dependency causing GC to never happen on this object
+            mBrowser = null;
         }
 
         @Override
         public void onConnectionFailed() {
             mConnState = DISCONNECTED;
+            // Remove what could be a circular dependency causing GC to never happen on this object
+            mBrowser = null;
             Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName
                     + ", Sending fail response!");
             mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
@@ -105,10 +120,11 @@
 
         @Override
         public void onConnectionSuspended() {
+            mBrowser = null;
             mConnState = SUSPENDED;
             Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
         }
-    };
+    }
 
     /* Subscription callback handler. Subscribe to a folder to get its contents */
     private MediaBrowser.SubscriptionCallback folderItemsCb =
@@ -251,26 +267,44 @@
     }
 
     /* initialize mediacontroller in order to communicate with media player. */
-    private void onBrowseConnect() {
-        boolean isError = false;
+    private void onBrowseConnect(String connectedPackage, MediaBrowser browser) {
+        if (!connectedPackage.equals(mConnectingPackageName)) {
+            Log.w(TAG, "onBrowseConnect: recieved callback for package we aren't connecting to "
+                            + connectedPackage);
+            return;
+        }
+        mConnectingPackageName = null;
+
+        if (browser == null) {
+            Log.e(TAG, "onBrowseConnect: received a null browser for " + connectedPackage);
+            mMediaInterface.setBrowsedPlayerRsp(
+                    mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
+            return;
+        }
+
         MediaSession.Token token = null;
         try {
-            /* get rootfolder uid from media player */
-            if (mMediaId == null) {
-                mMediaId = mMediaBrowser.getRoot();
-                /*
-                 * assuming that root folder uid will not change on uids changed
-                 */
-                mRootFolderUid = mMediaId;
-                /* store root folder uid to stack */
-                mPathStack.push(mMediaId);
-            }
-
-            if (!mMediaBrowser.isConnected()) {
+            if (!browser.isConnected()) {
                 Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "not connected");
-            } else if ((token = mMediaBrowser.getSessionToken()) == null) {
+            } else if ((token = browser.getSessionToken()) == null) {
                 Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "no Session token");
             } else {
+                /* update to the new MediaBrowser */
+                if (mMediaBrowser != null) mMediaBrowser.disconnect();
+                mMediaBrowser = browser;
+                mPackageName = connectedPackage;
+
+                /* get rootfolder uid from media player */
+                if (mMediaId == null) {
+                    mMediaId = mMediaBrowser.getRoot();
+                    /*
+                     * assuming that root folder uid will not change on uids changed
+                     */
+                    mRootFolderUid = mMediaId;
+                    /* store root folder uid to stack */
+                    mPathStack.push(mMediaId);
+                }
+
                 mMediaController = MediaController.wrap(
                     new android.media.session.MediaController(mContext, token));
                 /* get root folder items */
@@ -287,7 +321,7 @@
     }
 
     public void setBrowsed(String packageName, String cls) {
-        mPackageName = packageName;
+        mConnectingPackageName = packageName;
         mClassName = cls;
         /* cleanup variables from previous browsed calls */
         mFolderItems = null;
@@ -298,10 +332,14 @@
          * will be required while navigating up the folder
          */
         mPathStack = new Stack<String>();
+
         /* Bind to MediaBrowseService of MediaPlayer */
-        mMediaBrowser = new MediaBrowser(mContext, new ComponentName(mPackageName, mClassName),
-                browseMediaConnectionCallback, null);
-        mMediaBrowser.connect();
+        MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
+        MediaBrowser tempBrowser = new MediaBrowser(
+                mContext, new ComponentName(packageName, mClassName), callback, null);
+        callback.setBrowser(tempBrowser);
+
+        tempBrowser.connect();
     }
 
     /* called when connection to media player is closed */
@@ -494,7 +532,7 @@
      */
     private List<MediaBrowser.MediaItem> checkIndexOutofBounds(
             byte[] bdaddr, List<MediaBrowser.MediaItem> children, long startItem, long endItem) {
-        if (endItem > children.size()) endItem = children.size() - 1;
+        if (endItem >= children.size()) endItem = children.size() - 1;
         if (startItem >= Integer.MAX_VALUE) startItem = Integer.MAX_VALUE;
         try {
             List<MediaBrowser.MediaItem> childrenSubList =
@@ -651,9 +689,11 @@
 
                 case AvrcpConstants.ATTRID_GENRE:
                     attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
+                    break;
 
                 case AvrcpConstants.ATTRID_PLAY_TIME:
                     attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION);
+                    break;
 
                 case AvrcpConstants.ATTRID_COVER_ART:
                     Log.e(TAG, "getAttrValue: Cover art attribute not supported");