OSDN Git Service

AVRCP: Track players without active media sessions
authorMarie Janssen <jamuraa@google.com>
Tue, 2 May 2017 22:28:23 +0000 (15:28 -0700)
committerMarie Janssen <jamuraa@google.com>
Thu, 4 May 2017 00:50:58 +0000 (17:50 -0700)
Players can be selected as addressed by MediaSessionService, then
destroy and renew their MediaSession. We want them to continue to
be addressed.

Don't remove players that destroy their MediaSession, and just forget
the controller instead.

Remove players when their package is removed though.

Report "(unknown)" when we are asked for media info for an addressed
player that has no controller.

Fix a bug where we didn't send updated metadata when it changed.
Fix an ordering bug updating the media session.

Make logging more clear when we're adding / updating / removing players.
Add dumpsys log to confirm current mMediaController is correct.

Use @Nullable and @NonNull to help with distinguishing when
MediaController can be null in AddressedMediaPlayer.

Test: send pause / play from headset, look at bugreport to confirm
Change-Id: I083745f8988e67776716db2bec5e67b697e8ee67
Bug: 36357185
Fixes: 37865298
(cherry picked from commit 384011244f5a62ed34f198b8ba91341b4efb4fb6)

src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
src/com/android/bluetooth/avrcp/Avrcp.java
src/com/android/bluetooth/avrcp/mockable/MediaController.java

index d122675..def8def 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.avrcp;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAvrcp;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
@@ -42,15 +44,23 @@ public class AddressedMediaPlayer {
     static private final String TAG = "AddressedMediaPlayer";
     static private final Boolean DEBUG = false;
 
+    static private final long SINGLE_QID = 1;
+    static private final String UNKNOWN_TITLE = "(unknown)";
+
     private AvrcpMediaRspInterface mMediaInterface;
     private List<MediaSession.QueueItem> mNowPlayingList;
 
+    private final List<MediaSession.QueueItem> mUnknownNowPlayingList;
+
     private long mLastTrackIdSent;
 
     public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
         mNowPlayingList = null;
         mMediaInterface = mediaInterface;
         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
+        List<MediaSession.QueueItem> unknown = new ArrayList<MediaSession.QueueItem>();
+        unknown.add(getCurrentQueueItem(null, SINGLE_QID));
+        mUnknownNowPlayingList = unknown;
     }
 
     void cleanup() {
@@ -62,7 +72,7 @@ public class AddressedMediaPlayer {
 
     /* get now playing list from addressed player */
     void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
-            MediaController mediaController) {
+            @Nullable MediaController mediaController) {
         if (DEBUG) Log.v(TAG, "getFolderItemsNowPlaying");
         if (mediaController == null) {
             // No players (if a player exists, we would have selected it)
@@ -77,7 +87,7 @@ public class AddressedMediaPlayer {
 
     /* get item attributes for item in now playing list */
     void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
-            MediaController mediaController) {
+            @Nullable MediaController mediaController) {
         int status = AvrcpConstants.RSP_NO_ERROR;
         long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong();
         List<MediaSession.QueueItem> items = getNowPlayingList(mediaController);
@@ -87,12 +97,6 @@ public class AddressedMediaPlayer {
         if (Arrays.equals(itemAttr.mUid, AvrcpConstants.TRACK_IS_SELECTED)) {
             if (DEBUG) Log.d(TAG, "getItemAttr: Remote requests for now playing contents:");
 
-            if (mediaController == null) {
-                Log.e(TAG, "mediaController = null, sending no available players response");
-                mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
-                return;
-            }
-
             // get the current playing metadata and send.
             getItemAttrFilterAttr(bdaddr, itemAttr, getCurrentQueueItem(mediaController, mediaId),
                     mediaController);
@@ -113,10 +117,10 @@ public class AddressedMediaPlayer {
 
     /* Refresh and get the queue of now playing.
      */
-    private List<MediaSession.QueueItem> getNowPlayingList(MediaController mediaController) {
-        if (mediaController == null) return null;
+    private List<MediaSession.QueueItem> getNowPlayingList(
+            @Nullable MediaController mediaController) {
+        if (mediaController == null) return mUnknownNowPlayingList;
         if (mNowPlayingList != null) return mNowPlayingList;
-
         List<MediaSession.QueueItem> items = mediaController.getQueue();
         if (items == null) {
             Log.i(TAG, "null queue from " + mediaController.getPackageName()
@@ -124,7 +128,7 @@ public class AddressedMediaPlayer {
             MediaMetadata metadata = mediaController.getMetadata();
             // Because we are database-unaware, we can just number the item here whatever we want
             // because they have to re-poll it every time.
-            MediaSession.QueueItem current = getCurrentQueueItem(mediaController, 1);
+            MediaSession.QueueItem current = getCurrentQueueItem(mediaController, SINGLE_QID);
             items = new ArrayList<MediaSession.QueueItem>();
             items.add(current);
         }
@@ -135,7 +139,14 @@ public class AddressedMediaPlayer {
     /* Constructs a queue item representing the current playing metadata from an
      * active controller with queue id |qid|.
      */
-    private MediaSession.QueueItem getCurrentQueueItem(MediaController controller, long qid) {
+    private MediaSession.QueueItem getCurrentQueueItem(
+            @Nullable MediaController controller, long qid) {
+        if (controller == null) {
+            MediaDescription.Builder bob = new MediaDescription.Builder();
+            bob.setTitle(UNKNOWN_TITLE);
+            return new QueueItem(bob.build(), qid);
+        }
+
         MediaMetadata metadata = controller.getMetadata();
         if (metadata == null) {
             Log.w(TAG, "Controller has no metadata!? Making an empty one");
@@ -186,12 +197,12 @@ public class AddressedMediaPlayer {
     }
 
     /* Instructs media player to play particular media item */
-    void playItem(byte[] bdaddr, byte[] uid, MediaController mediaController) {
+    void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
         long qid = ByteBuffer.wrap(uid).getLong();
         List<MediaSession.QueueItem> items = mNowPlayingList;
 
         if (mediaController == null) {
-            Log.e(TAG, "mediaController is null");
+            Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
             mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
             return;
         }
@@ -218,19 +229,19 @@ public class AddressedMediaPlayer {
         mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
     }
 
-    void getTotalNumOfItems(byte[] bdaddr, MediaController mediaController) {
+    void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
         if (DEBUG) Log.d(TAG, "getTotalNumOfItems");
         List<MediaSession.QueueItem> items = mNowPlayingList;
         if (items != null) {
-            // We already have the cached list sending the response to remote
+            // We already have the cached list, send the response to remote
             mMediaInterface.getTotalNumOfItemsRsp(
                     bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
             return;
         }
 
         if (mediaController == null) {
-            Log.e(TAG, "mediaController = null, sending no available players response");
-            mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
+            Log.e(TAG, "getTotalNumOfItems with no mediaController, sending no items");
+            mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, 0);
             return;
         }
 
@@ -246,8 +257,9 @@ public class AddressedMediaPlayer {
         mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
     }
 
-    boolean sendTrackChangeWithId(boolean requesting, MediaController mediaController) {
-        if (DEBUG) Log.d(TAG, "sendTrackChangeWithId");
+    boolean sendTrackChangeWithId(boolean requesting, @Nullable MediaController mediaController) {
+        if (DEBUG)
+            Log.d(TAG, "sendTrackChangeWithId (" + requesting + "): controller " + mediaController);
         byte[] track;
         long qid = MediaSession.QueueItem.UNKNOWN_ID;
         if (mediaController != null) {
@@ -299,7 +311,7 @@ public class AddressedMediaPlayer {
      */
     private void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj,
             List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem,
-            MediaController mediaController) {
+            @NonNull MediaController mediaController) {
         if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = "
                 + endItem);
 
@@ -397,7 +409,7 @@ public class AddressedMediaPlayer {
     }
 
     private String getAttrValue(
-            int attr, MediaSession.QueueItem item, MediaController mediaController) {
+            int attr, MediaSession.QueueItem item, @Nullable MediaController mediaController) {
         String attrValue = null;
         if (item == null) {
             if (DEBUG) Log.d(TAG, "getAttrValue received null item");
@@ -405,11 +417,13 @@ public class AddressedMediaPlayer {
         }
         try {
             MediaDescription desc = item.getDescription();
-            PlaybackState state = mediaController.getPlaybackState();
             Bundle extras = desc.getExtras();
-            if (state != null && (item.getQueueId() == state.getActiveQueueItemId())) {
-                if (DEBUG) Log.d(TAG, "getAttrValue: item is active, filling extra data");
-                extras = fillBundle(mediaController.getMetadata(), extras);
+            if (mediaController != null) {
+                PlaybackState state = mediaController.getPlaybackState();
+                if (state != null && (item.getQueueId() == state.getActiveQueueItemId())) {
+                    if (DEBUG) Log.d(TAG, "getAttrValue: item is active, filling extra data");
+                    extras = fillBundle(mediaController.getMetadata(), extras);
+                }
             }
             if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
             switch (attr) {
@@ -466,7 +480,7 @@ public class AddressedMediaPlayer {
     }
 
     private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
-            MediaSession.QueueItem mediaItem, MediaController mediaController) {
+            MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController) {
         /* Response parameters */
         int[] attrIds = null; /* array of attr ids */
         String[] attrValues = null; /* array of attr values */
@@ -486,14 +500,15 @@ public class AddressedMediaPlayer {
             } else {
                 /* get only the requested attribute ids from the request */
                 for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
-                    if (DEBUG) Log.d(TAG, "getAttrValue: attr id[" + idx + "] :" +
-                        mItemAttrReqObj.mAttrIDs[idx]);
+                    if (DEBUG)
+                        Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
+                                        + mItemAttrReqObj.mAttrIDs[idx]);
                     attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
                 }
             }
         }
 
-        if (DEBUG) Log.d(TAG, "getAttrValue: attr id list size:" + attrTempId.size());
+        if (DEBUG) Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
         /* lookup and copy values of attributes for ids requested above */
         for (int idx = 0; idx < attrTempId.size(); idx++) {
             /* check if media player provided requested attributes */
index aa25f8b..cb1d2fb 100644 (file)
@@ -112,6 +112,8 @@ public final class Avrcp {
     private int mAbsVolRetryTimes;
     private int mSkipAmount;
 
+    private static final int NO_PLAYER_ID = 0;
+
     private int mCurrAddrPlayerID;
     private int mCurrBrowsePlayerID;
     private int mLastUsedPlayerID;
@@ -256,7 +258,7 @@ public final class Avrcp {
         mAbsVolThreshold = 0;
         mVolumeMapping = new HashMap<Integer, Integer>();
         sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
-        mCurrAddrPlayerID = 0;
+        mCurrAddrPlayerID = NO_PLAYER_ID;
         mCurrBrowsePlayerID = 0;
         mContext = context;
         mLastUsedPlayerID = 0;
@@ -374,6 +376,11 @@ public final class Avrcp {
         @Override
         public void onSessionDestroyed() {
             Log.v(TAG, "MediaController session destroyed");
+            if (mMediaController != null) {
+                removeMediaController(mMediaController.getWrappedInstance());
+                mMediaController.unregisterCallback(mMediaControllerCb);
+                mMediaController = null;
+            }
         }
 
         @Override
@@ -933,12 +940,14 @@ public final class Avrcp {
     }
 
     private void updateCurrentMediaState() {
+        MediaAttributes currentAttributes = mMediaAttributes;
+        PlaybackState newState = mCurrentPlayState;
         if (mMediaController == null) {
             // Use A2DP state if we don't have a MediaControlller
-            boolean isPlaying = (mA2dpState == BluetoothA2dp.STATE_PLAYING);
+            boolean isPlaying =
+                    (mA2dpState == BluetoothA2dp.STATE_PLAYING) && mAudioManager.isMusicActive();
             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,
@@ -947,26 +956,21 @@ public final class Avrcp {
                     builder.setState(PlaybackState.STATE_PAUSED,
                             PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
                 }
-                updatePlaybackState(builder.build());
+                newState = builder.build();
             }
-            // Can't get metadata from A2dp so we're done.
-            return;
+            mMediaAttributes = new MediaAttributes(null);
+        } else {
+            newState = mMediaController.getPlaybackState();
+            mMediaAttributes = new MediaAttributes(mMediaController.getMetadata());
         }
 
-        MediaAttributes currentAttributes = mMediaAttributes;
-
-        PlaybackState newState = mMediaController.getPlaybackState();
-
-        // Metadata
-        mMediaAttributes = new MediaAttributes(mMediaController.getMetadata());
-
-        if (currentAttributes.equals(mMediaAttributes)) {
+        if (!currentAttributes.equals(mMediaAttributes)) {
             Log.v(TAG, "MediaAttributes Changed to " + mMediaAttributes.toString());
             mTracksPlayed++;
             sendTrackChangedRsp(false);
         }
 
-        updatePlaybackState(mMediaController.getPlaybackState());
+        updatePlaybackState(newState);
     }
 
     private void getRcFeaturesRequestFromNative(byte[] address, int features) {
@@ -1409,6 +1413,7 @@ public final class Avrcp {
         if (DEBUG) Log.d(TAG, "packageName: " + packageName + " removed: " + removed);
 
         if (removed) {
+            removeMediaPlayerInfo(packageName);
             // old package is removed, updating local browsable player's list
             if (isBrowseSupported(packageName)) {
                 removePackageFromBrowseList(packageName);
@@ -1562,14 +1567,18 @@ public final class Avrcp {
                             getMediaControllers();
                     for (android.media.session.MediaController controller : currentControllers) {
                         if (!newControllers.contains(controller)) {
-                            removeMediaPlayerInfo(controller.getPackageName());
-                            playersChanged = true;
+                            removeMediaController(controller);
+                            if (mMediaController != null && mMediaController.equals(controller)) {
+                                if (DEBUG) Log.v(TAG, "Active Controller is gone!");
+                                mMediaController.unregisterCallback(mMediaControllerCb);
+                                mMediaController = null;
+                            }
                         }
                     }
 
                     if (playersChanged) {
                         mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
-                        if (newControllers.size() > 0 && (mMediaController == null)) {
+                        if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
                             if (DEBUG)
                                 Log.v(TAG,
                                         "No addressed player but active sessions, taking first.");
@@ -1794,7 +1803,6 @@ public final class Avrcp {
      *  @return true if an item was updated, false if it was added instead
      */
     private boolean addMediaPlayerInfo(MediaPlayerInfo info) {
-        if (DEBUG) Log.d(TAG, "add " + info.toString());
         int updateId = -1;
         boolean updated = false;
         synchronized (mMediaPlayerInfoList) {
@@ -1809,10 +1817,13 @@ public final class Avrcp {
                 // New player
                 mLastUsedPlayerID++;
                 updateId = mLastUsedPlayerID;
-            } else if (updateId == mCurrAddrPlayerID) {
-                updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
             }
             mMediaPlayerInfoList.put(updateId, info);
+            if (DEBUG)
+                Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
+            if (updateId == mCurrAddrPlayerID) {
+                updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
+            }
         }
         return updated;
     }
@@ -1828,6 +1839,8 @@ public final class Avrcp {
                 }
             }
             if (removeKey != -1) {
+                if (DEBUG)
+                    Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
                 return mMediaPlayerInfoList.remove(removeKey);
             }
 
@@ -1835,6 +1848,15 @@ public final class Avrcp {
         }
     }
 
+    /** Remove the controller referenced by |controller| from any player in the list */
+    private void removeMediaController(android.media.session.MediaController controller) {
+        synchronized (mMediaPlayerInfoList) {
+            for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
+                if (info.getMediaController().equals(controller)) info.setMediaController(null);
+            }
+        }
+    }
+
     /*
      * utility function to get the playback state of any media player through
      * media controller APIs.
@@ -2096,11 +2118,10 @@ public final class Avrcp {
 
         if (DEBUG)
             Log.d(TAG, "updateCurrentController: " + mMediaController + " to " + newController);
-        if (mMediaController == null || newController == null
-                || (mMediaController.getWrappedInstance() != newController.getWrappedInstance())) {
+        if (mMediaController == null || (!mMediaController.equals(newController))) {
             if (mMediaController != null) mMediaController.unregisterCallback(mMediaControllerCb);
-            if (newController != null) {
-                mMediaController = newController;
+            mMediaController = newController;
+            if (mMediaController != null) {
                 mMediaController.registerCallback(mMediaControllerCb, mHandler);
                 mAddressedMediaPlayer.updateNowPlayingList(mMediaController.getQueue());
             } else {
@@ -2183,13 +2204,17 @@ public final class Avrcp {
     }
 
     private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
-        if(itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
-            mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
-        }
-        else {
-            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress) != null)
+        if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+            if (mCurrAddrPlayerID == NO_PLAYER_ID) {
+                getItemAttrRspNative(
+                        itemAttr.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY, (byte) 0, null, null);
+            } else {
+                mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
+            }
+        } else {
+            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress) != null) {
                 mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress).getItemAttr(itemAttr);
-            else {
+            else {
                 Log.e(TAG, "Could not get attributes. mBrowsedMediaPlayer is null");
                 getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_INTERNAL_ERR,
                         (byte) 0, null, null);
@@ -2279,7 +2304,8 @@ public final class Avrcp {
         ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
         ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
         if (mMediaController != null)
-            ProfileService.println(sb, "mMediaSession pkg: " + mMediaController.getPackageName());
+            ProfileService.println(sb, "mMediaController: " + mMediaController.getWrappedInstance()
+                            + " pkg " + mMediaController.getPackageName());
 
         ProfileService.println(sb, "\nMedia Players:");
         synchronized (mMediaPlayerInfoList) {
index 032bf2b..30cc2bd 100644 (file)
@@ -139,6 +139,22 @@ public class MediaController {
         return mDelegate.controlsSameSession(other);
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof android.media.session.MediaController) {
+            return mDelegate.equals(o);
+        } else if (o instanceof MediaController) {
+            MediaController other = (MediaController) o;
+            return mDelegate.equals(other.mDelegate);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "(wraps " + mDelegate + ")";
+    }
+
     public static abstract class Callback extends android.media.session.MediaController.Callback { }
 
     public class TransportControls {