Merge cherrypicks of [2455372, 2455337, 2455612, 2455497] into oc-release

Change-Id: I05e7158e88bbdba30984eac8f4ab0eaf8cb0a55c
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 28a901a..8c94027 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -54,12 +54,14 @@
     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() {
@@ -67,12 +69,12 @@
         mNowPlayingList = mEmptyNowPlayingList;
         mMediaInterface = null;
         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
+        mNowPlayingListUpdated = false;
     }
 
     /* get now playing list from addressed player */
     void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
             @Nullable MediaController mediaController) {
-        if (DEBUG) Log.v(TAG, "getFolderItemsNowPlaying");
         if (mediaController == null) {
             // No players (if a player exists, we would have selected it)
             Log.e(TAG, "mediaController = null, sending no available players response");
@@ -120,7 +122,10 @@
             @Nullable MediaController mediaController) {
         if (mediaController == null) return mEmptyNowPlayingList;
         List<MediaSession.QueueItem> items = mediaController.getQueue();
-        if (items == mNowPlayingList) return mNowPlayingList;
+        if (items != null && !mNowPlayingListUpdated) {
+            mNowPlayingList = items;
+            return mNowPlayingList;
+        }
         if (items == null) {
             Log.i(TAG, "null queue from " + mediaController.getPackageName()
                             + ", constructing single-item list");
@@ -131,12 +136,19 @@
             items = new ArrayList<MediaSession.QueueItem>();
             items.add(current);
         }
+
         mNowPlayingList = items;
-        // TODO (jamuraa): test to see if the single-item queue is the same and don't send
-        if (mMediaInterface != null) {
-            mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
-        }
-        return items;
+
+        if (mNowPlayingListUpdated) sendNowPlayingListChanged();
+
+        return mNowPlayingList;
+    }
+
+    private void sendNowPlayingListChanged() {
+        if (mMediaInterface == null) return;
+        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
@@ -196,6 +208,7 @@
     }
 
     void updateNowPlayingList(@Nullable MediaController mediaController) {
+        mNowPlayingListUpdated = true;
         getNowPlayingList(mediaController);
     }
 
@@ -239,14 +252,13 @@
     }
 
     void sendTrackChangeWithId(int type, @Nullable MediaController mediaController) {
-        if (DEBUG)
-            Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
+        Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
         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;
-        // The nowPlaying might have changed.
-        updateNowPlayingList(mediaController);
     }
 
     /*
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index 9c286e2..712b2a8 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -86,8 +86,11 @@
     private @NonNull PlaybackState mCurrentPlayState;
     private int mA2dpState;
     private int mPlayStatusChangedNT;
+    private int mReportedPlayStatus;
     private int mTrackChangedNT;
     private int mPlayPosChangedNT;
+    private int mAddrPlayerChangedNT;
+    private int mReportedPlayerID;
     private long mPlaybackIntervalMs;
     private long mLastReportedPosition;
     private long mNextPosMs;
@@ -121,7 +124,7 @@
     private AvrcpMediaRsp mAvrcpMediaRsp;
 
     /* UID counter to be shared across different files. */
-    static short sUIDCounter;
+    static short sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
 
     /* BTRC features */
     public static final int BTRC_FEAT_METADATA = 0x01;
@@ -157,14 +160,12 @@
     private static final int MSG_ADJUST_VOLUME = 15;
     private static final int MSG_SET_ABSOLUTE_VOLUME = 16;
     private static final int MSG_ABS_VOL_TIMEOUT = 17;
-    private static final int MSG_FAST_FORWARD = 18;
-    private static final int MSG_REWIND = 19;
-    private static final int MSG_SET_A2DP_AUDIO_STATE = 20;
-    private static final int MSG_ADDRESSED_PLAYER_CHANGED_RSP = 21;
-    private static final int MSG_AVAILABLE_PLAYERS_CHANGED_RSP = 22;
-    private static final int MSG_NOW_PLAYING_CHANGED_RSP = 23;
+    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;
@@ -177,6 +178,7 @@
 
     /* List of Media player instances, useful for retrieving MediaPlayerList or MediaPlayerInfo */
     private SortedMap<Integer, MediaPlayerInfo> mMediaPlayerInfoList;
+    private boolean mAvailablePlayerViewChanged;
 
     /* List of media players which supports browse */
     private List<BrowsePlayerInfo> mBrowsePlayerInfoList;
@@ -236,11 +238,13 @@
         mMediaAttributes = new MediaAttributes(null);
         mLastQueueId = MediaSession.QueueItem.UNKNOWN_ID;
         mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
+        mReportedPlayStatus = PLAYSTATUS_ERROR;
         mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
         mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        mPlaybackIntervalMs = 0L;
         mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        mPlaybackIntervalMs = 0L;
         mLastReportedPosition = -1;
         mNextPosMs = -1;
         mPrevPosMs = -1;
@@ -256,8 +260,8 @@
         mLastLocalVolume = -1;
         mAbsVolThreshold = 0;
         mVolumeMapping = new HashMap<Integer, Integer>();
-        sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
         mCurrAddrPlayerID = NO_PLAYER_ID;
+        mReportedPlayerID = mCurrAddrPlayerID;
         mCurrBrowsePlayerID = 0;
         mContext = context;
         mLastUsedPlayerID = 0;
@@ -300,6 +304,7 @@
         mMediaControllerCb = new MediaControllerListener();
         mAvrcpMediaRsp = new AvrcpMediaRsp();
         mMediaPlayerInfoList = new TreeMap<Integer, MediaPlayerInfo>();
+        mAvailablePlayerViewChanged = false;
         mBrowsePlayerInfoList = Collections.synchronizedList(new ArrayList<BrowsePlayerInfo>());
         mPassthroughDispatched = 0;
         mPassthroughLogs = new EvictingQueue<MediaKeyLog>(PASSTHROUGH_LOG_MAX_SIZE);
@@ -368,12 +373,12 @@
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
             if (DEBUG) Log.v(TAG, "onMetadataChanged");
-            updateCurrentMediaState(false);
+            scheduleMediaUpdate();
         }
         @Override
         public synchronized void onPlaybackStateChanged(PlaybackState state) {
             if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
-            updateCurrentMediaState(false);
+            scheduleMediaUpdate();
         }
 
         @Override
@@ -427,7 +432,7 @@
             case MSG_NATIVE_REQ_GET_PLAY_STATUS:
             {
                 byte[] address = (byte[]) msg.obj;
-                int btstate = convertPlayStateToPlayStatus(mCurrentPlayState);
+                int btstate = getBluetoothPlayState(mCurrentPlayState);
                 int length = (int) mMediaAttributes.getLength();
                 int position = (int) getPlayPosition();
                 if (DEBUG)
@@ -470,31 +475,12 @@
                 processRegisterNotification((byte[]) msg.obj, msg.arg1, msg.arg2);
                 break;
 
-            case MSG_AVAILABLE_PLAYERS_CHANGED_RSP:
-                if (DEBUG) Log.v(TAG, "MSG_AVAILABLE_PLAYERS_CHANGED_RSP");
-                removeMessages(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
-                registerNotificationRspAvalPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
-                break;
-
             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);
                 break;
 
-            case MSG_ADDRESSED_PLAYER_CHANGED_RSP:
-                // Later addressed players override earlier ones.
-                if (hasMessages(MSG_ADDRESSED_PLAYER_CHANGED_RSP)) {
-                    Log.i(TAG, "MSG_ADDRESSED_PLAYER_CHANGED_RSP: skip, more changes in queue");
-                    break;
-                }
-                if (DEBUG)
-                    Log.v(TAG, "MSG_ADDRESSED_PLAYER_CHANGED_RSP: newAddrPlayer = " + msg.arg1);
-                registerNotificationRspAddrPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED, msg.arg1, sUIDCounter);
-                break;
-
             case MSG_PLAY_INTERVAL_TIMEOUT:
                 sendPlayPosNotificationRsp(false);
                 break;
@@ -706,7 +692,7 @@
             case MSG_SET_A2DP_AUDIO_STATE:
                 if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
                 mA2dpState = msg.arg1;
-                updateCurrentMediaState(false);
+                scheduleMediaUpdate();
                 break;
 
             case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
@@ -792,6 +778,13 @@
                 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;
@@ -799,40 +792,59 @@
         }
     }
 
-    private void updatePlaybackState(PlaybackState state) {
-        if (state == null) {
-          state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
-                PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
+    private PlaybackState updatePlaybackState() {
+        PlaybackState newState = new PlaybackState.Builder()
+                                         .setState(PlaybackState.STATE_NONE,
+                                                 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f)
+                                         .build();
+        synchronized (this) {
+            PlaybackState controllerState = null;
+            if (mMediaController != null) {
+                controllerState = mMediaController.getPlaybackState();
+            }
+
+            if (controllerState != null) {
+                newState = controllerState;
+            } else if (mAudioManager != null && mAudioManager.isMusicActive()) {
+                // Use A2DP state if we don't have a state from MediaControlller
+                PlaybackState.Builder builder = new PlaybackState.Builder();
+                if (mA2dpState == BluetoothA2dp.STATE_PLAYING) {
+                    builder.setState(PlaybackState.STATE_PLAYING,
+                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
+                } else {
+                    builder.setState(PlaybackState.STATE_PAUSED,
+                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
+                }
+                newState = builder.build();
+            }
         }
 
-        byte stateBytes = (byte) convertPlayStateToBytes(state.getState());
+        byte newPlayStatus = getBluetoothPlayState(newState);
 
-        /* updating play status in global media player list */
+        /* update play status in global media player list */
         MediaPlayerInfo player = getAddressedPlayerInfo();
         if (player != null) {
-            player.setPlayStatus(stateBytes);
-        } else {
-            Log.w(TAG, "onPlaybackStateChanged: no addressed player id=" + mCurrAddrPlayerID);
+            player.setPlayStatus(newPlayStatus);
         }
 
-        int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
-        int newPlayStatus = convertPlayStateToPlayStatus(state);
-
         if (DEBUG) {
-            Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): "+
-                    "old=" + mCurrentPlayState + "(" + oldPlayStatus + "), "+
-                    "new=" + state + "(" + newPlayStatus + ")");
+            Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): " + mReportedPlayStatus
+                            + "➡" + newPlayStatus + "(" + newState + ")");
         }
 
-        mCurrentPlayState = state;
+        if (newState != null) mCurrentPlayState = newState;
 
-        sendPlayPosNotificationRsp(false);
-
-        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM &&
-                (oldPlayStatus != newPlayStatus)) {
-            mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-            registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus);
+        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
+                && (mReportedPlayStatus != newPlayStatus)) {
+            sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
         }
+        return mCurrentPlayState;
+    }
+
+    private void sendPlaybackStatus(int playStatusChangedNT, byte playbackState) {
+        registerNotificationRspPlayStatusNative(playStatusChangedNT, playbackState);
+        mPlayStatusChangedNT = playStatusChangedNT;
+        mReportedPlayStatus = playbackState;
     }
 
     private void updateTransportControls(int transportControlFlags) {
@@ -962,23 +974,40 @@
         }
     }
 
+    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) {
+            if (mAvailablePlayerViewChanged) {
+                registerNotificationRspAvalPlayerChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+                mAvailablePlayerViewChanged = false;
+            }
+            if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
+                    && mReportedPlayerID != mCurrAddrPlayerID) {
+                registerNotificationRspAddrPlayerChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter);
+                mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                // Changing player sends reject to anything else we would notify...
+                mReportedPlayerID = mCurrAddrPlayerID;
+                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                // If the player changed, they need to re-request anything here again
+                // so we can skip the rest of the update.
+                return;
+            }
+        }
+
         MediaAttributes currentAttributes;
-        PlaybackState newState = null;
+        PlaybackState newState = updatePlaybackState();
+
         synchronized (this) {
             if (mMediaController == null) {
-                // Use A2DP state if we don't have a MediaControlller
-                boolean isPlaying = (mA2dpState == BluetoothA2dp.STATE_PLAYING)
-                        && mAudioManager.isMusicActive();
-                PlaybackState.Builder builder = new PlaybackState.Builder();
-                if (isPlaying) {
-                    builder.setState(PlaybackState.STATE_PLAYING,
-                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
-                } else {
-                    builder.setState(PlaybackState.STATE_PAUSED,
-                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
-                }
-                newState = builder.build();
                 currentAttributes = new MediaAttributes(null);
             } else {
                 newState = mMediaController.getPlaybackState();
@@ -1000,8 +1029,7 @@
             mMediaAttributes = currentAttributes;
             mLastQueueId = newQueueId;
         }
-
-        updatePlaybackState(newState);
+        sendPlayPosNotificationRsp(false);
     }
 
     private void getRcFeaturesRequestFromNative(byte[] address, int features) {
@@ -1033,9 +1061,10 @@
     private void processRegisterNotification(byte[] address, int eventId, int param) {
         switch (eventId) {
             case EVT_PLAY_STATUS_CHANGED:
-                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
-                        convertPlayStateToPlayStatus(mCurrentPlayState));
+                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+                updatePlaybackState();
+                sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
+                        getBluetoothPlayState(mCurrentPlayState));
                 break;
 
             case EVT_TRACK_CHANGED:
@@ -1063,6 +1092,8 @@
                 registerNotificationRspAddrPlayerChangedNative(
                         AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
                         mCurrAddrPlayerID, sUIDCounter);
+                mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
+                mReportedPlayerID = mCurrAddrPlayerID;
                 break;
 
             case EVENT_UIDS_CHANGED:
@@ -1127,45 +1158,10 @@
         return mCurrentPlayState.getPosition();
     }
 
-    private int convertPlayStateToPlayStatus(PlaybackState state) {
-        int playStatus = PLAYSTATUS_ERROR;
-        switch (state.getState()) {
-            case PlaybackState.STATE_PLAYING:
-            case PlaybackState.STATE_BUFFERING:
-                playStatus = PLAYSTATUS_PLAYING;
-                break;
-
-            case PlaybackState.STATE_STOPPED:
-            case PlaybackState.STATE_NONE:
-                playStatus = PLAYSTATUS_STOPPED;
-                break;
-
-            case PlaybackState.STATE_PAUSED:
-                playStatus = PLAYSTATUS_PAUSED;
-                break;
-
-            case PlaybackState.STATE_FAST_FORWARDING:
-            case PlaybackState.STATE_SKIPPING_TO_NEXT:
-            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
-                playStatus = PLAYSTATUS_FWD_SEEK;
-                break;
-
-            case PlaybackState.STATE_REWINDING:
-            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
-                playStatus = PLAYSTATUS_REV_SEEK;
-                break;
-
-            case PlaybackState.STATE_ERROR:
-                playStatus = PLAYSTATUS_ERROR;
-                break;
-
-        }
-        return playStatus;
-    }
-
-    private boolean isPlayingState(PlaybackState state) {
-        return (state.getState() == PlaybackState.STATE_PLAYING) ||
-                (state.getState() == PlaybackState.STATE_BUFFERING);
+    private boolean isPlayingState(@Nullable PlaybackState state) {
+        if (state == null) return false;
+        return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING)
+                || (state.getState() == PlaybackState.STATE_BUFFERING);
     }
 
     /**
@@ -1586,23 +1582,17 @@
                 @Override
                 public void onActiveSessionsChanged(
                         List<android.media.session.MediaController> newControllers) {
-                    boolean playersChanged = false;
-
                     // Update the current players
                     for (android.media.session.MediaController controller : newControllers) {
                         addMediaPlayerController(controller);
-                        playersChanged = true;
                     }
 
-                    if (playersChanged) {
-                        mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
-                        if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
-                            if (DEBUG)
-                                Log.v(TAG,
-                                        "No addressed player but active sessions, taking first.");
-                            setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
-                        }
+                    if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
+                        if (DEBUG)
+                            Log.v(TAG, "No addressed player but active sessions, taking first.");
+                        setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
                     }
+                    scheduleMediaUpdate();
                 }
             };
 
@@ -1618,7 +1608,7 @@
         // If the player doesn't exist, we need to add it.
         if (getMediaPlayerInfo(packageName) == null) {
             addMediaPlayerPackage(packageName);
-            mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
+            scheduleMediaUpdate();
         }
         synchronized (mMediaPlayerInfoList) {
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
@@ -1626,8 +1616,7 @@
                     int newAddrID = entry.getKey();
                     if (DEBUG) Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
                     updateCurrentController(newAddrID, mCurrBrowsePlayerID);
-                    mHandler.obtainMessage(MSG_ADDRESSED_PLAYER_CHANGED_RSP, newAddrID, 0)
-                            .sendToTarget();
+                    scheduleMediaUpdate();
                     return;
                 }
             }
@@ -1765,9 +1754,8 @@
             for (android.media.session.MediaController controller : controllers) {
                 addMediaPlayerController(controller);
             }
-            if (controllers.size() > 0) {
-                mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
-            }
+
+            scheduleMediaUpdate();
 
             if (mMediaPlayerInfoList.size() > 0) {
                 // Set the first one as the Addressed Player
@@ -1793,7 +1781,7 @@
     /** Add (or update) a player to the media player list without a controller */
     private boolean addMediaPlayerPackage(String packageName) {
         MediaPlayerInfo info = new MediaPlayerInfo(null, AvrcpConstants.PLAYER_TYPE_AUDIO,
-                AvrcpConstants.PLAYER_SUBTYPE_NONE, getPlayStateBytes(null),
+                AvrcpConstants.PLAYER_SUBTYPE_NONE, PLAYSTATUS_STOPPED,
                 getFeatureBitMask(packageName), packageName, getAppLabel(packageName));
         return addMediaPlayerInfo(info);
     }
@@ -1803,8 +1791,9 @@
         String packageName = controller.getPackageName();
         MediaPlayerInfo info = new MediaPlayerInfo(MediaController.wrap(controller),
                 AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
-                getPlayStateBytes(controller.getPlaybackState()), getFeatureBitMask(packageName),
-                controller.getPackageName(), getAppLabel(packageName));
+                getBluetoothPlayState(controller.getPlaybackState()),
+                getFeatureBitMask(packageName), controller.getPackageName(),
+                getAppLabel(packageName));
         return addMediaPlayerInfo(info);
     }
 
@@ -1814,10 +1803,20 @@
     private boolean addMediaPlayerInfo(MediaPlayerInfo info) {
         int updateId = -1;
         boolean updated = false;
+        boolean currentRemoved = false;
         synchronized (mMediaPlayerInfoList) {
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-                if (info.getPackageName().equals(entry.getValue().getPackageName())) {
-                    updateId = entry.getKey();
+                MediaPlayerInfo current = entry.getValue();
+                int id = entry.getKey();
+                if (info.getPackageName().equals(current.getPackageName())) {
+                    if (!current.equalView(info)) {
+                        // If we would present a different player, make it a new player
+                        // so that controllers know whether a player is browsable or not.
+                        mMediaPlayerInfoList.remove(id);
+                        currentRemoved = (mCurrAddrPlayerID == id);
+                        break;
+                    }
+                    updateId = id;
                     updated = true;
                     break;
                 }
@@ -1826,12 +1825,13 @@
                 // New player
                 mLastUsedPlayerID++;
                 updateId = mLastUsedPlayerID;
+                mAvailablePlayerViewChanged = true;
             }
             mMediaPlayerInfoList.put(updateId, info);
             if (DEBUG)
                 Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
-            if (updateId == mCurrAddrPlayerID) {
-                updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
+            if (currentRemoved || updateId == mCurrAddrPlayerID) {
+                updateCurrentController(updateId, mCurrBrowsePlayerID);
             }
         }
         return updated;
@@ -1850,6 +1850,7 @@
             if (removeKey != -1) {
                 if (DEBUG)
                     Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
+                mAvailablePlayerViewChanged = true;
                 return mMediaPlayerInfoList.remove(removeKey);
             }
 
@@ -1878,25 +1879,13 @@
      * utility function to get the playback state of any media player through
      * media controller APIs.
      */
-    private byte getPlayStateBytes(PlaybackState pbState) {
-        byte playStateBytes = PLAYSTATUS_STOPPED;
-
-        if (pbState != null) {
-            playStateBytes = (byte)convertPlayStateToBytes(pbState.getState());
-            Log.v(TAG, "getPlayBackState: playStateBytes = " + playStateBytes);
-        } else {
-            Log.w(TAG, "playState object null, sending playStateBytes = " + playStateBytes);
+    private byte getBluetoothPlayState(PlaybackState pbState) {
+        if (pbState == null) {
+            Log.w(TAG, "playState object null, sending STOPPED");
+            return PLAYSTATUS_STOPPED;
         }
 
-        return playStateBytes;
-    }
-
-    /*
-     * utility function to map framework's play state values to AVRCP spec
-     * defined play status values
-     */
-    private int convertPlayStateToBytes(int playState) {
-        switch (playState) {
+        switch (pbState.getState()) {
             case PlaybackState.STATE_PLAYING:
             case PlaybackState.STATE_BUFFERING:
                 return PLAYSTATUS_PLAYING;
@@ -2143,7 +2132,7 @@
                 }
             }
         }
-        updateCurrentMediaState(false);
+        scheduleMediaUpdate();
         return registerRsp;
     }
 
@@ -2507,7 +2496,7 @@
             }
         }
 
-        public void uidsChangedRsp(byte[] address, int type, int uidCounter) {
+        public void uidsChangedRsp(int type) {
             if (!registerNotificationRspUIDsChangedNative(type, sUIDCounter)) {
                 Log.e(TAG, "registerNotificationRspUIDsChangedNative failed!");
             }
@@ -2720,12 +2709,12 @@
     // Do not modify without updating the HAL bt_rc.h files.
 
     // match up with btrc_play_status_t enum of bt_rc.h
-    final static int PLAYSTATUS_STOPPED = 0;
-    final static int PLAYSTATUS_PLAYING = 1;
-    final static int PLAYSTATUS_PAUSED = 2;
-    final static int PLAYSTATUS_FWD_SEEK = 3;
-    final static int PLAYSTATUS_REV_SEEK = 4;
-    final static int PLAYSTATUS_ERROR = 255;
+    final static byte PLAYSTATUS_STOPPED = 0;
+    final static byte PLAYSTATUS_PLAYING = 1;
+    final static byte PLAYSTATUS_PAUSED = 2;
+    final static byte PLAYSTATUS_FWD_SEEK = 3;
+    final static byte PLAYSTATUS_REV_SEEK = 4;
+    final static byte PLAYSTATUS_ERROR = (byte) 255;
 
     // match up with btrc_media_attr_t enum of bt_rc.h
     final static int MEDIA_ATTR_TITLE = 1;
diff --git a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
index 5a5b5f7..b0a4c5d 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.avrcp;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.media.session.MediaSession;
 
 import com.android.bluetooth.Utils;
@@ -200,17 +202,19 @@
     private int subType;
     private byte playStatus;
     private short[] featureBitMask;
-    private String packageName;
-    private String displayableName;
-    private MediaController mediaController;
+    private @NonNull String packageName;
+    private @NonNull String displayableName;
+    private @Nullable MediaController mediaController;
 
-    MediaPlayerInfo(MediaController controller, byte majorType, int subType, byte playStatus,
-            short[] featureBitMask, String packageName, String displayableName) {
+    MediaPlayerInfo(@Nullable MediaController controller, byte majorType, int subType,
+            byte playStatus, short[] featureBitMask, @NonNull String packageName,
+            @Nullable String displayableName) {
         this.setMajorType(majorType);
         this.setSubType(subType);
         this.playStatus = playStatus;
         // store a copy the FeatureBitMask array
         this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
+        Arrays.sort(this.featureBitMask);
         this.setPackageName(packageName);
         this.setDisplayableName(displayableName);
         this.setMediaController(controller);
@@ -236,7 +240,7 @@
         this.mediaController = mediaController;
     }
 
-    void setPackageName(String name) {
+    void setPackageName(@NonNull String name) {
         // Controller determines package name when it is set.
         if (mediaController != null) return;
         this.packageName = name;
@@ -271,7 +275,8 @@
         return displayableName;
     }
 
-    void setDisplayableName(String displayableName) {
+    void setDisplayableName(@Nullable String displayableName) {
+        if (displayableName == null) displayableName = "";
         this.displayableName = displayableName;
     }
 
@@ -282,6 +287,7 @@
     void setFeatureBitMask(short[] featureBitMask) {
         synchronized (this) {
             this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
+            Arrays.sort(this.featureBitMask);
         }
     }
 
@@ -295,6 +301,14 @@
         return false;
     }
 
+    /** Tests if the view of this player presented to the controller is different enough to
+     *  justify sending an Available Players Changed update */
+    public boolean equalView(MediaPlayerInfo other) {
+        return (this.majorType == other.getMajorType()) && (this.subType == other.getSubType())
+                && Arrays.equals(this.featureBitMask, other.getFeatureBitMask())
+                && this.displayableName.equals(other.getDisplayableName());
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
index 71493b6..1c7b87c 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
@@ -45,7 +45,7 @@
 
     public void avalPlayerChangedRsp(byte[] address, int type);
 
-    public void uidsChangedRsp(byte[] address, int type, int uidCounter);
+    public void uidsChangedRsp(int type);
 
     public void nowPlayingChangedRsp(int type);