Home | History | Annotate | Download | only in avrcp
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.bluetooth.avrcp;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.media.MediaDescription;
     22 import android.media.MediaMetadata;
     23 import android.media.session.MediaSession;
     24 import android.media.session.MediaSession.QueueItem;
     25 import android.media.session.PlaybackState;
     26 import android.os.Bundle;
     27 import android.util.Log;
     28 
     29 import com.android.bluetooth.Utils;
     30 import com.android.bluetooth.btservice.ProfileService;
     31 
     32 import java.nio.ByteBuffer;
     33 import java.util.ArrayList;
     34 import java.util.Arrays;
     35 import java.util.List;
     36 
     37 /*************************************************************************************************
     38  * Provides functionality required for Addressed Media Player, like Now Playing List related
     39  * browsing commands, control commands to the current addressed player(playItem, play, pause, etc)
     40  * Acts as an Interface to communicate with media controller APIs for NowPlayingItems.
     41  ************************************************************************************************/
     42 
     43 public class AddressedMediaPlayer {
     44     private static final String TAG = "AddressedMediaPlayer";
     45     private static final Boolean DEBUG = false;
     46 
     47     private static final long SINGLE_QID = 1;
     48     private static final String UNKNOWN_TITLE = "(unknown)";
     49 
     50     static private final String GPM_BUNDLE_METADATA_KEY =
     51             "com.google.android.music.mediasession.music_metadata";
     52 
     53     private AvrcpMediaRspInterface mMediaInterface;
     54     @NonNull private List<MediaSession.QueueItem> mNowPlayingList;
     55 
     56     private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
     57 
     58     private long mLastTrackIdSent;
     59 
     60     public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
     61         mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>();
     62         mNowPlayingList = mEmptyNowPlayingList;
     63         mMediaInterface = mediaInterface;
     64         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
     65     }
     66 
     67     void cleanup() {
     68         if (DEBUG) {
     69             Log.v(TAG, "cleanup");
     70         }
     71         mNowPlayingList = mEmptyNowPlayingList;
     72         mMediaInterface = null;
     73         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
     74     }
     75 
     76     /* get now playing list from addressed player */
     77     void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
     78             @Nullable MediaController mediaController) {
     79         if (mediaController == null) {
     80             // No players (if a player exists, we would have selected it)
     81             Log.e(TAG, "mediaController = null, sending no available players response");
     82             mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
     83             return;
     84         }
     85         List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
     86         getFolderItemsFilterAttr(bdaddr, reqObj, items, AvrcpConstants.BTRC_SCOPE_NOW_PLAYING,
     87                 reqObj.mStartItem, reqObj.mEndItem, mediaController);
     88     }
     89 
     90     /* get item attributes for item in now playing list */
     91     void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
     92             @Nullable MediaController mediaController) {
     93         int status = AvrcpConstants.RSP_NO_ERROR;
     94         long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong();
     95         List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
     96 
     97         // NOTE: this is out-of-spec (AVRCP 1.6.1 sec 6.10.4.3, p90) but we answer it anyway
     98         // because some CTs ask for it.
     99         if (Arrays.equals(itemAttr.mUid, AvrcpConstants.TRACK_IS_SELECTED)) {
    100             mediaId = getActiveQueueItemId(mediaController);
    101             if (DEBUG) {
    102                 Log.d(TAG, "getItemAttr: Remote requests for now playing contents, sending UID: "
    103                         + mediaId);
    104             }
    105         }
    106 
    107         if (DEBUG) {
    108             Log.d(TAG, "getItemAttr-UID: 0x" + Utils.byteArrayToString(itemAttr.mUid));
    109         }
    110         for (MediaSession.QueueItem item : items) {
    111             if (item.getQueueId() == mediaId) {
    112                 getItemAttrFilterAttr(bdaddr, itemAttr, item, mediaController);
    113                 return;
    114             }
    115         }
    116 
    117         // Couldn't find it, so the id is invalid
    118         mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM, null);
    119     }
    120 
    121     /* Refresh and get the queue of now playing.
    122      */
    123     @NonNull
    124     List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
    125         if (mediaController == null) {
    126             return mEmptyNowPlayingList;
    127         }
    128         List<MediaSession.QueueItem> items = mediaController.getQueue();
    129         if (items == null) {
    130             Log.i(TAG, "null queue from " + mediaController.getPackageName()
    131                     + ", constructing single-item list");
    132 
    133             // Because we are database-unaware, we can just number the item here whatever we want
    134             // because they have to re-poll it every time.
    135             MediaMetadata metadata = mediaController.getMetadata();
    136             if (metadata == null) {
    137                 Log.w(TAG, "Controller has no metadata!? Making an empty one");
    138                 metadata = (new MediaMetadata.Builder()).build();
    139             }
    140 
    141             MediaDescription.Builder bob = new MediaDescription.Builder();
    142             MediaDescription desc = metadata.getDescription();
    143 
    144             // set the simple ones that MediaMetadata builds for us
    145             bob.setMediaId(desc.getMediaId());
    146             bob.setTitle(desc.getTitle());
    147             bob.setSubtitle(desc.getSubtitle());
    148             bob.setDescription(desc.getDescription());
    149             // fill the ones that we use later
    150             bob.setExtras(fillBundle(metadata, desc.getExtras()));
    151 
    152             // build queue item with the new metadata
    153             MediaSession.QueueItem current = new QueueItem(bob.build(), SINGLE_QID);
    154 
    155             items = new ArrayList<MediaSession.QueueItem>();
    156             items.add(current);
    157         }
    158 
    159         if (!items.equals(mNowPlayingList)) {
    160             sendNowPlayingListChanged();
    161         }
    162         mNowPlayingList = items;
    163 
    164         return mNowPlayingList;
    165     }
    166 
    167     private void sendNowPlayingListChanged() {
    168         if (mMediaInterface == null) {
    169             return;
    170         }
    171         if (DEBUG) {
    172             Log.d(TAG, "sendNowPlayingListChanged()");
    173         }
    174         mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
    175     }
    176 
    177     private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) {
    178         if (metadata == null) {
    179             Log.i(TAG, "fillBundle: metadata is null");
    180             return currentExtras;
    181         }
    182 
    183         Bundle bundle = currentExtras;
    184         if (bundle == null) {
    185             bundle = new Bundle();
    186         }
    187 
    188         String[] stringKeys = {
    189                 MediaMetadata.METADATA_KEY_TITLE,
    190                 MediaMetadata.METADATA_KEY_ARTIST,
    191                 MediaMetadata.METADATA_KEY_ALBUM,
    192                 MediaMetadata.METADATA_KEY_GENRE
    193         };
    194         for (String key : stringKeys) {
    195             String current = bundle.getString(key);
    196             if (current == null) {
    197                 bundle.putString(key, metadata.getString(key));
    198             }
    199         }
    200 
    201         String[] longKeys = {
    202                 MediaMetadata.METADATA_KEY_TRACK_NUMBER,
    203                 MediaMetadata.METADATA_KEY_NUM_TRACKS,
    204                 MediaMetadata.METADATA_KEY_DURATION
    205         };
    206         for (String key : longKeys) {
    207             if (!bundle.containsKey(key)) {
    208                 bundle.putLong(key, metadata.getLong(key));
    209             }
    210         }
    211         return bundle;
    212     }
    213 
    214     /* Instructs media player to play particular media item */
    215     void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
    216         long qid = ByteBuffer.wrap(uid).getLong();
    217         List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
    218 
    219         if (mediaController == null) {
    220             Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
    221             mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
    222             return;
    223         }
    224 
    225         MediaController.TransportControls mediaControllerCntrl =
    226                 mediaController.getTransportControls();
    227 
    228         if (items == null) {
    229             Log.w(TAG, "nowPlayingItems is null");
    230             mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
    231             return;
    232         }
    233 
    234         for (MediaSession.QueueItem item : items) {
    235             if (qid == item.getQueueId()) {
    236                 if (DEBUG) {
    237                     Log.d(TAG, "Skipping to ID " + qid);
    238                 }
    239                 mediaControllerCntrl.skipToQueueItem(qid);
    240                 mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
    241                 return;
    242             }
    243         }
    244 
    245         Log.w(TAG, "Invalid now playing Queue ID " + qid);
    246         mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
    247     }
    248 
    249     void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
    250         List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
    251         if (DEBUG) {
    252             Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
    253         }
    254         mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
    255     }
    256 
    257     void sendTrackChangeWithId(int type, @Nullable MediaController mediaController) {
    258         Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
    259         long qid = getActiveQueueItemId(mediaController);
    260         byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
    261         // The nowPlayingList changed: the new list has the full data for the current item
    262         mMediaInterface.trackChangedRsp(type, track);
    263         mLastTrackIdSent = qid;
    264     }
    265 
    266     /*
    267      * helper method to check if startItem and endItem index is with range of
    268      * MediaItem list. (Resultset containing all items in current path)
    269      */
    270     @Nullable
    271     private List<MediaSession.QueueItem> getQueueSubset(@NonNull List<MediaSession.QueueItem> items,
    272             long startItem, long endItem) {
    273         if (endItem > items.size()) {
    274             endItem = items.size() - 1;
    275         }
    276         if (startItem > Integer.MAX_VALUE) {
    277             startItem = Integer.MAX_VALUE;
    278         }
    279         try {
    280             List<MediaSession.QueueItem> selected =
    281                     items.subList((int) startItem, (int) Math.min(items.size(), endItem + 1));
    282             if (selected.isEmpty()) {
    283                 Log.i(TAG, "itemsSubList is empty.");
    284                 return null;
    285             }
    286             return selected;
    287         } catch (IndexOutOfBoundsException ex) {
    288             Log.i(TAG, "Range (" + startItem + ", " + endItem + ") invalid");
    289         } catch (IllegalArgumentException ex) {
    290             Log.i(TAG, "Range start " + startItem + " > size (" + items.size() + ")");
    291         }
    292         return null;
    293     }
    294 
    295     /*
    296      * helper method to filter required attibutes before sending GetFolderItems
    297      * response
    298      */
    299     private void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj,
    300             @NonNull List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem,
    301             @NonNull MediaController mediaController) {
    302         if (DEBUG) {
    303             Log.d(TAG,
    304                     "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
    305         }
    306 
    307         List<MediaSession.QueueItem> resultItems = getQueueSubset(items, startItem, endItem);
    308         /* check for index out of bound errors */
    309         if (resultItems == null) {
    310             Log.w(TAG, "getFolderItemsFilterAttr: resultItems is empty");
    311             mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
    312             return;
    313         }
    314 
    315         FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
    316 
    317         /* variables to accumulate attrs */
    318         ArrayList<String> attrArray = new ArrayList<String>();
    319         ArrayList<Integer> attrId = new ArrayList<Integer>();
    320 
    321         for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
    322             MediaSession.QueueItem item = resultItems.get(itemIndex);
    323             // get the queue id
    324             long qid = item.getQueueId();
    325             byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
    326 
    327             // get the array of uid from 2d to array 1D array
    328             for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
    329                 folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
    330             }
    331 
    332             /* Set display name for current item */
    333             folderDataNative.mDisplayNames[itemIndex] =
    334                     getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController);
    335 
    336             int maxAttributesRequested = 0;
    337             boolean isAllAttribRequested = false;
    338             /* check if remote requested for attributes */
    339             if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
    340                 int attrCnt = 0;
    341 
    342                 /* add requested attr ids to a temp array */
    343                 if (folderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
    344                     isAllAttribRequested = true;
    345                     maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
    346                 } else {
    347                     /* get only the requested attribute ids from the request */
    348                     maxAttributesRequested = folderItemsReqObj.mNumAttr;
    349                 }
    350 
    351                 /* lookup and copy values of attributes for ids requested above */
    352                 for (int idx = 0; idx < maxAttributesRequested; idx++) {
    353                     /* check if media player provided requested attributes */
    354                     String value = null;
    355 
    356                     int attribId =
    357                             isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx];
    358                     value = getAttrValue(attribId, item, mediaController);
    359                     if (value != null) {
    360                         attrArray.add(value);
    361                         attrId.add(attribId);
    362                         attrCnt++;
    363                     }
    364                 }
    365                 /* add num attr actually received from media player for a particular item */
    366                 folderDataNative.mAttributesNum[itemIndex] = attrCnt;
    367             }
    368         }
    369 
    370         /* copy filtered attr ids and attr values to response parameters */
    371         if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
    372             folderDataNative.mAttrIds = new int[attrId.size()];
    373             for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
    374                 folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
    375             }
    376             folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
    377         }
    378         for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++) {
    379             if (DEBUG) {
    380                 Log.d(TAG, "folderDataNative.mAttributesNum"
    381                         + folderDataNative.mAttributesNum[attrIndex] + " attrIndex " + attrIndex);
    382             }
    383         }
    384 
    385         /* create rsp object and send response to remote device */
    386         FolderItemsRsp rspObj =
    387                 new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
    388                         folderDataNative.mNumItems, folderDataNative.mFolderTypes,
    389                         folderDataNative.mPlayable, folderDataNative.mItemTypes,
    390                         folderDataNative.mItemUid, folderDataNative.mDisplayNames,
    391                         folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
    392                         folderDataNative.mAttrValues);
    393         mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
    394     }
    395 
    396     private String getAttrValue(int attr, MediaSession.QueueItem item,
    397             @Nullable MediaController mediaController) {
    398         String attrValue = null;
    399         if (item == null) {
    400             if (DEBUG) {
    401                 Log.d(TAG, "getAttrValue received null item");
    402             }
    403             return null;
    404         }
    405         try {
    406             MediaDescription desc = item.getDescription();
    407             Bundle extras = desc.getExtras();
    408             boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
    409             MediaMetadata data = null;
    410             if (isCurrentTrack) {
    411                 if (DEBUG) {
    412                     Log.d(TAG, "getAttrValue: item is active, using current data");
    413                 }
    414                 data = mediaController.getMetadata();
    415                 if (data == null) {
    416                     Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
    417                 }
    418             }
    419 
    420             if (data == null) {
    421                 // TODO: This code can be removed when b/63117921 is resolved
    422                 data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY);
    423                 extras = null; // We no longer need the data in here
    424             }
    425 
    426             extras = fillBundle(data, extras);
    427 
    428             if (DEBUG) {
    429                 Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
    430             }
    431             switch (attr) {
    432                 case AvrcpConstants.ATTRID_TITLE:
    433                     /* Title is mandatory attribute */
    434                     if (isCurrentTrack) {
    435                         attrValue = extras.getString(MediaMetadata.METADATA_KEY_TITLE);
    436                     } else {
    437                         attrValue = desc.getTitle().toString();
    438                     }
    439                     break;
    440 
    441                 case AvrcpConstants.ATTRID_ARTIST:
    442                     attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
    443                     break;
    444 
    445                 case AvrcpConstants.ATTRID_ALBUM:
    446                     attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
    447                     break;
    448 
    449                 case AvrcpConstants.ATTRID_TRACK_NUM:
    450                     attrValue =
    451                             Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
    452                     break;
    453 
    454                 case AvrcpConstants.ATTRID_NUM_TRACKS:
    455                     attrValue =
    456                             Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
    457                     break;
    458 
    459                 case AvrcpConstants.ATTRID_GENRE:
    460                     attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
    461                     break;
    462 
    463                 case AvrcpConstants.ATTRID_PLAY_TIME:
    464                     attrValue = Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_DURATION));
    465                     break;
    466 
    467                 case AvrcpConstants.ATTRID_COVER_ART:
    468                     Log.e(TAG, "getAttrValue: Cover art attribute not supported");
    469                     return null;
    470 
    471                 default:
    472                     Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
    473                     return null;
    474             }
    475         } catch (NullPointerException ex) {
    476             Log.w(TAG, "getAttrValue: attr id not found in result");
    477             /* checking if attribute is title, then it is mandatory and cannot send null */
    478             if (attr == AvrcpConstants.ATTRID_TITLE) {
    479                 attrValue = "<Unknown Title>";
    480             } else {
    481                 return null;
    482             }
    483         }
    484         if (DEBUG) {
    485             Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
    486         }
    487         return attrValue;
    488     }
    489 
    490     private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
    491             MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController) {
    492         /* Response parameters */
    493         int[] attrIds = null; /* array of attr ids */
    494         String[] attrValues = null; /* array of attr values */
    495 
    496         /* variables to temperorily add attrs */
    497         ArrayList<String> attrArray = new ArrayList<String>();
    498         ArrayList<Integer> attrId = new ArrayList<Integer>();
    499         ArrayList<Integer> attrTempId = new ArrayList<Integer>();
    500 
    501         /* check if remote device has requested for attributes */
    502         if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
    503             if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
    504                 for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
    505                     attrTempId.add(idx); /* attr id 0x00 is unused */
    506                 }
    507             } else {
    508                 /* get only the requested attribute ids from the request */
    509                 for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
    510                     if (DEBUG) {
    511                         Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
    512                                 + mItemAttrReqObj.mAttrIDs[idx]);
    513                     }
    514                     attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
    515                 }
    516             }
    517         }
    518 
    519         if (DEBUG) {
    520             Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
    521         }
    522         /* lookup and copy values of attributes for ids requested above */
    523         for (int idx = 0; idx < attrTempId.size(); idx++) {
    524             /* check if media player provided requested attributes */
    525             String value = getAttrValue(attrTempId.get(idx), mediaItem, mediaController);
    526             if (value != null) {
    527                 attrArray.add(value);
    528                 attrId.add(attrTempId.get(idx));
    529             }
    530         }
    531 
    532         /* copy filtered attr ids and attr values to response parameters */
    533         if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
    534             attrIds = new int[attrId.size()];
    535 
    536             for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
    537                 attrIds[attrIndex] = attrId.get(attrIndex);
    538             }
    539 
    540             attrValues = attrArray.toArray(new String[attrId.size()]);
    541 
    542             /* create rsp object and send response */
    543             ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
    544             mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
    545             return;
    546         }
    547     }
    548 
    549     private long getActiveQueueItemId(@Nullable MediaController controller) {
    550         if (controller == null) {
    551             return MediaSession.QueueItem.UNKNOWN_ID;
    552         }
    553         PlaybackState state = controller.getPlaybackState();
    554         if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
    555                 || state.getState() == PlaybackState.STATE_NONE) {
    556             return MediaSession.QueueItem.UNKNOWN_ID;
    557         }
    558         long qid = state.getActiveQueueItemId();
    559         if (qid != MediaSession.QueueItem.UNKNOWN_ID) {
    560             return qid;
    561         }
    562         // Check if we're presenting a "one item queue"
    563         if (controller.getMetadata() != null) {
    564             return SINGLE_QID;
    565         }
    566         return MediaSession.QueueItem.UNKNOWN_ID;
    567     }
    568 
    569     String displayMediaItem(MediaSession.QueueItem item) {
    570         StringBuilder sb = new StringBuilder();
    571         sb.append("#");
    572         sb.append(item.getQueueId());
    573         sb.append(": ");
    574         sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_TITLE, item, null)));
    575         sb.append(" - ");
    576         sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ALBUM, item, null)));
    577         sb.append(" by ");
    578         sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ARTIST, item, null)));
    579         sb.append(" (");
    580         sb.append(getAttrValue(AvrcpConstants.ATTRID_PLAY_TIME, item, null));
    581         sb.append(" ");
    582         sb.append(getAttrValue(AvrcpConstants.ATTRID_TRACK_NUM, item, null));
    583         sb.append("/");
    584         sb.append(getAttrValue(AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
    585         sb.append(") ");
    586         sb.append(getAttrValue(AvrcpConstants.ATTRID_GENRE, item, null));
    587         return sb.toString();
    588     }
    589 
    590     public void dump(StringBuilder sb, @Nullable MediaController mediaController) {
    591         ProfileService.println(sb, "AddressedPlayer info:");
    592         ProfileService.println(sb, "mLastTrackIdSent: " + mLastTrackIdSent);
    593         ProfileService.println(sb, "mNowPlayingList: " + mNowPlayingList.size() + " elements");
    594         long currentQueueId = getActiveQueueItemId(mediaController);
    595         for (MediaSession.QueueItem item : mNowPlayingList) {
    596             long itemId = item.getQueueId();
    597             ProfileService.println(sb,
    598                     (itemId == currentQueueId ? "*" : " ") + displayMediaItem(item));
    599         }
    600     }
    601 }
    602