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