AVRCP: update metadata and state more consistently

When we get onMetadata it's not updated quite right, and the
PlaybackState gives the wrong answer for getActiveQueueItemId, which
means that we don't send the correct updated data.  Update the metadata
when we get a onPlaybackStateChanged as well, so we refresh the
metadata and notify correctly for EVENT_TRACK_CHANGED.

Keep track of the last queue item we sent and don't send an update if
the queue item has not changed.

Use A2DP streaming state only when we don't have strong signals from
Media framework.

Use PlaybackState.getLastPositionUpdateTime() instead of trying to
figure it out on our own.

Trust MediaAttributes with the track length instead of duplicating it.

Test: connect to BMW kit and skip some tracks, new data will show up
correctly
Bug: 37209435
Bug: 36016671
Bug: 36357185
Bug: 36055995
Change-Id: I332aaadeabcfe8284084a34c964e5f0d07bfeef4

(cherry picked from commit 0a429916782c20980e7f0893c503c633b8341f88)
(cherry picked from commit 93f177151f5b96ce3e06888884da3cd7f7858ecf)
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 02d077f..d122675 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -45,15 +45,19 @@
     private AvrcpMediaRspInterface mMediaInterface;
     private List<MediaSession.QueueItem> mNowPlayingList;
 
+    private long mLastTrackIdSent;
+
     public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
         mNowPlayingList = null;
         mMediaInterface = mediaInterface;
+        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
     }
 
     void cleanup() {
         if (DEBUG) Log.v(TAG, "cleanup");
         mNowPlayingList = null;
         mMediaInterface = null;
+        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
     }
 
     /* get now playing list from addressed player */
@@ -242,22 +246,27 @@
         mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
     }
 
-    void sendTrackChangeWithId(int trackChangedNT, MediaController mediaController) {
+    boolean sendTrackChangeWithId(boolean requesting, MediaController mediaController) {
         if (DEBUG) Log.d(TAG, "sendTrackChangeWithId");
         byte[] track;
-        if (mediaController == null) {
-            mMediaInterface.trackChangedRsp(trackChangedNT, AvrcpConstants.NO_TRACK_SELECTED);
-            return;
-        }
         long qid = MediaSession.QueueItem.UNKNOWN_ID;
-        PlaybackState state = mediaController.getPlaybackState();
-        if (state != null) {
-            qid = state.getActiveQueueItemId();
+        if (mediaController != null) {
+            PlaybackState state = mediaController.getPlaybackState();
+            /* for any item associated with NowPlaying, uid is queueId */
+            if (state != null) qid = state.getActiveQueueItemId();
         }
-        /* for any item associated with NowPlaying, uid is queueId */
+        if (!requesting && qid == mLastTrackIdSent) {
+            if (DEBUG) Log.d(TAG, "not sending duplicate track changed id " + qid);
+            return false;
+        }
         track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
         if (DEBUG) Log.d(TAG, "trackChangedRsp: 0x" + Utils.byteArrayToString(track));
+
+        int trackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        if (requesting) trackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
         mMediaInterface.trackChangedRsp(trackChangedNT, track);
+        mLastTrackIdSent = qid;
+        return (trackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
     }
 
     /*
@@ -418,11 +427,13 @@
                     break;
 
                 case AvrcpConstants.ATTRID_TRACK_NUM:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+                    attrValue =
+                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
                     break;
 
                 case AvrcpConstants.ATTRID_NUM_TRACKS:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+                    attrValue =
+                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
                     break;
 
                 case AvrcpConstants.ATTRID_GENRE:
@@ -430,7 +441,7 @@
                     break;
 
                 case AvrcpConstants.ATTRID_PLAY_TIME:
-                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION);
+                    attrValue = Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_DURATION));
                     break;
 
                 case AvrcpConstants.ATTRID_COVER_ART:
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index 2c084d0..f589026 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -81,12 +81,11 @@
     private PackageManager mPackageManager;
     private int mTransportControlFlags;
     private PlaybackState mCurrentPlayState;
-    private long mLastStateUpdate;
+    private int mA2dpState;
     private int mPlayStatusChangedNT;
     private int mTrackChangedNT;
     private int mPlayPosChangedNT;
     private long mTracksPlayed;
-    private long mSongLengthMs;
     private long mPlaybackIntervalMs;
     private long mLastReportedPosition;
     private long mNextPosMs;
@@ -235,11 +234,10 @@
     private Avrcp(Context context) {
         mMediaAttributes = new MediaAttributes(null);
         mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
+        mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
         mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mTracksPlayed = 0;
-        mLastStateUpdate = -1L;
-        mSongLengthMs = 0L;
         mPlaybackIntervalMs = 0L;
         mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         mLastReportedPosition = -1;
@@ -364,24 +362,13 @@
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
             Log.v(TAG, "MediaController metadata changed");
-            updateMetadata(metadata);
+            updateCurrentMediaState();
         }
 
         @Override
         public synchronized void onPlaybackStateChanged(PlaybackState state) {
             if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
-
-            updatePlaybackState(state);
-
-            byte stateBytes = (byte) convertPlayStateToBytes(state.getState());
-
-            /* updating 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);
-            }
+            updateCurrentMediaState();
         }
 
         @Override
@@ -434,7 +421,7 @@
                 byte[] address = (byte[]) msg.obj;
                 if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_PLAY_STATUS");
                 getPlayStatusRspNative(address, convertPlayStateToPlayStatus(mCurrentPlayState),
-                        (int) mSongLengthMs, (int) getPlayPosition());
+                        (int) mMediaAttributes.getLength(), (int) getPlayPosition());
                 break;
             }
 
@@ -699,7 +686,8 @@
 
             case MSG_SET_A2DP_AUDIO_STATE:
                 if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
-                updateA2dpAudioState(msg.arg1);
+                mA2dpState = msg.arg1;
+                updateCurrentMediaState();
                 break;
 
             case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
@@ -792,30 +780,22 @@
         }
     }
 
-    private void updateA2dpAudioState(int state) {
-        boolean isPlaying = (state == BluetoothA2dp.STATE_PLAYING);
-        if (isPlaying != isPlayingState(mCurrentPlayState)) {
-            /* if a2dp is streaming, check to make sure music is active */
-            if (isPlaying && !mAudioManager.isMusicActive())
-                return;
-            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);
-            }
-            updatePlaybackState(builder.build());
-        }
-    }
-
     private void updatePlaybackState(PlaybackState state) {
         if (state == null) {
           state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
                 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
         }
 
+        byte stateBytes = (byte) convertPlayStateToBytes(state.getState());
+
+        /* updating 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);
+        }
+
         int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
         int newPlayStatus = convertPlayStateToPlayStatus(state);
 
@@ -826,7 +806,6 @@
         }
 
         mCurrentPlayState = state;
-        mLastStateUpdate = SystemClock.elapsedRealtime();
 
         sendPlayPosNotificationRsp(false);
 
@@ -849,7 +828,7 @@
         private String mediaNumber;
         private String mediaTotalNumber;
         private String genre;
-        private String playingTimeMs;
+        private long playingTimeMs;
 
         private static final int ATTR_TITLE = 1;
         private static final int ATTR_ARTIST_NAME = 2;
@@ -870,7 +849,7 @@
             mediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
             mediaTotalNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
             genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
-            playingTimeMs = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_DURATION));
+            playingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
 
             // Try harder for the title.
             title = data.getString(MediaMetadata.METADATA_KEY_TITLE);
@@ -888,6 +867,11 @@
                 title = new String();
         }
 
+        public long getLength() {
+            if (!exists) return 0L;
+            return playingTimeMs;
+        }
+
         public boolean equals(MediaAttributes other) {
             if (other == null)
                 return false;
@@ -898,13 +882,11 @@
             if (exists == false)
                 return true;
 
-            return (title.equals(other.title)) &&
-                (artistName.equals(other.artistName)) &&
-                (albumName.equals(other.albumName)) &&
-                (mediaNumber.equals(other.mediaNumber)) &&
-                (mediaTotalNumber.equals(other.mediaTotalNumber)) &&
-                (genre.equals(other.genre)) &&
-                (playingTimeMs.equals(other.playingTimeMs));
+            return (title.equals(other.title)) && (artistName.equals(other.artistName))
+                    && (albumName.equals(other.albumName))
+                    && (mediaNumber.equals(other.mediaNumber))
+                    && (mediaTotalNumber.equals(other.mediaTotalNumber))
+                    && (genre.equals(other.genre)) && (playingTimeMs == other.playingTimeMs);
         }
 
         public String getString(int attrId) {
@@ -925,7 +907,7 @@
                 case ATTR_GENRE:
                     return genre;
                 case ATTR_PLAYING_TIME_MS:
-                    return playingTimeMs;
+                    return Long.toString(playingTimeMs);
                 default:
                     return new String();
             }
@@ -944,40 +926,47 @@
                 return "[MediaAttributes: none]";
             }
 
-            return "[MediaAttributes: " + title + " - " + albumName + " by "
-                    + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") "
-                    + genre + "]";
+            return "[MediaAttributes: " + title + " - " + albumName + " by " + artistName + " ("
+                    + playingTimeMs + " " + mediaNumber + "/" + mediaTotalNumber + ") " + genre
+                    + "]";
         }
     }
 
-    private void updateMetadata(MediaMetadata data) {
-        MediaAttributes oldAttributes = mMediaAttributes;
-        mMediaAttributes = new MediaAttributes(data);
-        if (data == null) {
-            mSongLengthMs = 0L;
-        } else {
-            mSongLengthMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
+    private void updateCurrentMediaState() {
+        if (mMediaController == null) {
+            // Use A2DP state if we don't have a MediaControlller
+            boolean isPlaying = (mA2dpState == BluetoothA2dp.STATE_PLAYING);
+            if (isPlaying != isPlayingState(mCurrentPlayState)) {
+                /* if a2dp is streaming, check to make sure music is active */
+                if (isPlaying && !mAudioManager.isMusicActive()) return;
+                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);
+                }
+                updatePlaybackState(builder.build());
+            }
+            // Can't get metadata from A2dp so we're done.
+            return;
         }
 
-        if (!oldAttributes.equals(mMediaAttributes)) {
+        MediaAttributes currentAttributes = mMediaAttributes;
+
+        PlaybackState newState = mMediaController.getPlaybackState();
+
+        // Metadata
+        mMediaAttributes = new MediaAttributes(mMediaController.getMetadata());
+
+        if (currentAttributes.equals(mMediaAttributes)) {
             Log.v(TAG, "MediaAttributes Changed to " + mMediaAttributes.toString());
             mTracksPlayed++;
-
-            if (mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
-                mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-                sendTrackChangedRsp();
-            }
-        } else {
-            Log.v(TAG, "Updated " + mMediaAttributes.toString() + " but no change!");
+            sendTrackChangedRsp(false);
         }
 
-        // Update the play state, which sends play state and play position
-        // notifications if needed.
-        if (mMediaController != null) {
-            updatePlaybackState(mMediaController.getPlaybackState());
-        } else {
-            updatePlaybackState(null);
-        }
+        updatePlaybackState(mMediaController.getPlaybackState());
     }
 
     private void getRcFeaturesRequestFromNative(byte[] address, int features) {
@@ -1021,7 +1010,7 @@
             case EVT_TRACK_CHANGED:
                 Log.v(TAG, "Track changed notification enabled");
                 mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
-                sendTrackChangedRsp();
+                sendTrackChangedRsp(true);
                 break;
 
             case EVT_PLAY_POS_CHANGED:
@@ -1068,22 +1057,25 @@
         mHandler.sendMessage(msg);
     }
 
-    private void sendTrackChangedRsp() {
+    private void sendTrackChangedRsp(boolean requested) {
         MediaPlayerInfo info = getAddressedPlayerInfo();
         if (info != null && !info.isBrowseSupported()) {
             // for players which does not support Browse or when no track is currently selected
-            trackChangeRspForBrowseUnsupported();
+            trackChangeRspForBrowseUnsupported(requested);
         } else {
+            boolean changed =
+                    mAddressedMediaPlayer.sendTrackChangeWithId(requested, mMediaController);
             // for players which support browsing
-            mAddressedMediaPlayer.sendTrackChangeWithId(mTrackChangedNT, mMediaController);
+            if (changed) mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         }
     }
 
-    private void trackChangeRspForBrowseUnsupported() {
+    private void trackChangeRspForBrowseUnsupported(boolean requested) {
         byte[] track = AvrcpConstants.TRACK_IS_SELECTED;
-        if (mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
-                && !mMediaAttributes.exists) {
+        if (requested && !mMediaAttributes.exists) {
             track = AvrcpConstants.NO_TRACK_SELECTED;
+        } else if (!requested) {
+            mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         }
         registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
     }
@@ -1098,7 +1090,9 @@
         }
 
         if (isPlayingState(mCurrentPlayState)) {
-            return SystemClock.elapsedRealtime() - mLastStateUpdate + mCurrentPlayState.getPosition();
+            long sinceUpdate =
+                    (SystemClock.elapsedRealtime() - mCurrentPlayState.getLastPositionUpdateTime());
+            return sinceUpdate + mCurrentPlayState.getPosition();
         }
 
         return mCurrentPlayState.getPosition();
@@ -2105,13 +2099,12 @@
             if (newController != null) {
                 mMediaController = newController;
                 mMediaController.registerCallback(mMediaControllerCb, mHandler);
-                updateMetadata(mMediaController.getMetadata());
                 mAddressedMediaPlayer.updateNowPlayingList(mMediaController.getQueue());
             } else {
-                updateMetadata(null);
                 mAddressedMediaPlayer.updateNowPlayingList(null);
                 registerRsp = false;
             }
+            updateCurrentMediaState();
         }
         return registerRsp;
     }
@@ -2264,10 +2257,8 @@
         ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
         ProfileService.println(sb, "mTracksPlayed: " + mTracksPlayed);
         ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
-        ProfileService.println(sb, "mLastStateUpdate: " + mLastStateUpdate);
         ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
         ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT);
-        ProfileService.println(sb, "mSongLengthMs: " + mSongLengthMs);
         ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs);
         ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT);
         ProfileService.println(sb, "mNextPosMs: " + mNextPosMs);