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    static private final String GPM_BUNDLE_METADATA_KEY =
52            "com.google.android.music.mediasession.music_metadata";
53
54    private AvrcpMediaRspInterface mMediaInterface;
55    private @NonNull List<MediaSession.QueueItem> mNowPlayingList;
56
57    private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
58
59    private long mLastTrackIdSent;
60
61    public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
62        mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>();
63        mNowPlayingList = mEmptyNowPlayingList;
64        mMediaInterface = mediaInterface;
65        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
66    }
67
68    void cleanup() {
69        if (DEBUG) Log.v(TAG, "cleanup");
70        mNowPlayingList = mEmptyNowPlayingList;
71        mMediaInterface = null;
72        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
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 = updateNowPlayingList(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 = updateNowPlayingList(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            mediaId = getActiveQueueItemId(mediaController);
100            if (DEBUG) {
101                Log.d(TAG,
102                        "getItemAttr: Remote requests for now playing contents, sending UID: "
103                                + mediaId);
104            }
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    @NonNull
122    List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
123        if (mediaController == null) return mEmptyNowPlayingList;
124        List<MediaSession.QueueItem> items = mediaController.getQueue();
125        if (items == null) {
126            Log.i(TAG, "null queue from " + mediaController.getPackageName()
127                            + ", constructing single-item list");
128
129            // Because we are database-unaware, we can just number the item here whatever we want
130            // because they have to re-poll it every time.
131            MediaMetadata metadata = mediaController.getMetadata();
132            if (metadata == null) {
133                Log.w(TAG, "Controller has no metadata!? Making an empty one");
134                metadata = (new MediaMetadata.Builder()).build();
135            }
136
137            MediaDescription.Builder bob = new MediaDescription.Builder();
138            MediaDescription desc = metadata.getDescription();
139
140            // set the simple ones that MediaMetadata builds for us
141            bob.setMediaId(desc.getMediaId());
142            bob.setTitle(desc.getTitle());
143            bob.setSubtitle(desc.getSubtitle());
144            bob.setDescription(desc.getDescription());
145            // fill the ones that we use later
146            bob.setExtras(fillBundle(metadata, desc.getExtras()));
147
148            // build queue item with the new metadata
149            MediaSession.QueueItem current = new QueueItem(bob.build(), SINGLE_QID);
150
151            items = new ArrayList<MediaSession.QueueItem>();
152            items.add(current);
153        }
154
155        if (!items.equals(mNowPlayingList)) sendNowPlayingListChanged();
156        mNowPlayingList = items;
157
158        return mNowPlayingList;
159    }
160
161    private void sendNowPlayingListChanged() {
162        if (mMediaInterface == null) return;
163        if (DEBUG) Log.d(TAG, "sendNowPlayingListChanged()");
164        mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
165    }
166
167    private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) {
168        if (metadata == null) {
169            Log.i(TAG, "fillBundle: metadata is null");
170            return currentExtras;
171        }
172
173        Bundle bundle = currentExtras;
174        if (bundle == null) bundle = new Bundle();
175
176        String[] stringKeys = {MediaMetadata.METADATA_KEY_TITLE, MediaMetadata.METADATA_KEY_ARTIST,
177                MediaMetadata.METADATA_KEY_ALBUM, MediaMetadata.METADATA_KEY_GENRE};
178        for (String key : stringKeys) {
179            String current = bundle.getString(key);
180            if (current == null) bundle.putString(key, metadata.getString(key));
181        }
182
183        String[] longKeys = {MediaMetadata.METADATA_KEY_TRACK_NUMBER,
184                MediaMetadata.METADATA_KEY_NUM_TRACKS, MediaMetadata.METADATA_KEY_DURATION};
185        for (String key : longKeys) {
186            if (!bundle.containsKey(key)) bundle.putLong(key, metadata.getLong(key));
187        }
188        return bundle;
189    }
190
191    /* Instructs media player to play particular media item */
192    void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
193        long qid = ByteBuffer.wrap(uid).getLong();
194        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
195
196        if (mediaController == null) {
197            Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
198            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
199            return;
200        }
201
202        MediaController.TransportControls mediaControllerCntrl =
203                mediaController.getTransportControls();
204
205        if (items == null) {
206            Log.w(TAG, "nowPlayingItems is null");
207            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
208            return;
209        }
210
211        for (MediaSession.QueueItem item : items) {
212            if (qid == item.getQueueId()) {
213                if (DEBUG) Log.d(TAG, "Skipping to ID " + qid);
214                mediaControllerCntrl.skipToQueueItem(qid);
215                mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
216                return;
217            }
218        }
219
220        Log.w(TAG, "Invalid now playing Queue ID " + qid);
221        mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
222    }
223
224    void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
225        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
226        if (DEBUG) Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
227        mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
228    }
229
230    void sendTrackChangeWithId(int type, @Nullable MediaController mediaController) {
231        Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
232        long qid = getActiveQueueItemId(mediaController);
233        byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
234        // The nowPlayingList changed: the new list has the full data for the current item
235        mMediaInterface.trackChangedRsp(type, track);
236        mLastTrackIdSent = qid;
237    }
238
239    /*
240     * helper method to check if startItem and endItem index is with range of
241     * MediaItem list. (Resultset containing all items in current path)
242     */
243    private @Nullable List<MediaSession.QueueItem> getQueueSubset(
244            @NonNull List<MediaSession.QueueItem> items, long startItem, long endItem) {
245        if (endItem > items.size()) endItem = items.size() - 1;
246        if (startItem > Integer.MAX_VALUE) startItem = Integer.MAX_VALUE;
247        try {
248            List<MediaSession.QueueItem> selected =
249                    items.subList((int) startItem, (int) Math.min(items.size(), endItem + 1));
250            if (selected.isEmpty()) {
251                Log.i(TAG, "itemsSubList is empty.");
252                return null;
253            }
254            return selected;
255        } catch (IndexOutOfBoundsException ex) {
256            Log.i(TAG, "Range (" + startItem + ", " + endItem + ") invalid");
257        } catch (IllegalArgumentException ex) {
258            Log.i(TAG, "Range start " + startItem + " > size (" + items.size() + ")");
259        }
260        return null;
261    }
262
263    /*
264     * helper method to filter required attibutes before sending GetFolderItems
265     * response
266     */
267    private void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj,
268            @NonNull List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem,
269            @NonNull MediaController mediaController) {
270        if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = "
271                + endItem);
272
273        List<MediaSession.QueueItem> result_items = getQueueSubset(items, startItem, endItem);
274        /* check for index out of bound errors */
275        if (result_items == null) {
276            Log.w(TAG, "getFolderItemsFilterAttr: result_items is empty");
277            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
278            return;
279        }
280
281        FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
282
283        /* variables to accumulate attrs */
284        ArrayList<String> attrArray = new ArrayList<String>();
285        ArrayList<Integer> attrId = new ArrayList<Integer>();
286
287        for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) {
288            MediaSession.QueueItem item = result_items.get(itemIndex);
289            // get the queue id
290            long qid = item.getQueueId();
291            byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
292
293            // get the array of uid from 2d to array 1D array
294            for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
295                folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
296            }
297
298            /* Set display name for current item */
299            folderDataNative.mDisplayNames[itemIndex] =
300                    getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController);
301
302            int maxAttributesRequested = 0;
303            boolean isAllAttribRequested = false;
304            /* check if remote requested for attributes */
305            if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
306                int attrCnt = 0;
307
308                /* add requested attr ids to a temp array */
309                if (folderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
310                    isAllAttribRequested = true;
311                    maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
312                } else {
313                    /* get only the requested attribute ids from the request */
314                    maxAttributesRequested = folderItemsReqObj.mNumAttr;
315                }
316
317                /* lookup and copy values of attributes for ids requested above */
318                for (int idx = 0; idx < maxAttributesRequested; idx++) {
319                    /* check if media player provided requested attributes */
320                    String value = null;
321
322                    int attribId =
323                            isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx];
324                    value = getAttrValue(attribId, item, mediaController);
325                    if (value != null) {
326                        attrArray.add(value);
327                        attrId.add(attribId);
328                        attrCnt++;
329                    }
330                }
331                /* add num attr actually received from media player for a particular item */
332                folderDataNative.mAttributesNum[itemIndex] = attrCnt;
333            }
334        }
335
336        /* copy filtered attr ids and attr values to response parameters */
337        if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
338            folderDataNative.mAttrIds = new int[attrId.size()];
339            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
340                folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
341            folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
342        }
343        for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++)
344            if (DEBUG)
345                Log.d(TAG, "folderDataNative.mAttributesNum"
346                                + folderDataNative.mAttributesNum[attrIndex] + " attrIndex "
347                                + attrIndex);
348
349        /* create rsp object and send response to remote device */
350        FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter,
351                scope, folderDataNative.mNumItems, folderDataNative.mFolderTypes,
352                folderDataNative.mPlayable, folderDataNative.mItemTypes, folderDataNative.mItemUid,
353                folderDataNative.mDisplayNames, folderDataNative.mAttributesNum,
354                folderDataNative.mAttrIds, folderDataNative.mAttrValues);
355        mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
356    }
357
358    private String getAttrValue(
359            int attr, MediaSession.QueueItem item, @Nullable MediaController mediaController) {
360        String attrValue = null;
361        if (item == null) {
362            if (DEBUG) Log.d(TAG, "getAttrValue received null item");
363            return null;
364        }
365        try {
366            MediaDescription desc = item.getDescription();
367            Bundle extras = desc.getExtras();
368            boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
369            MediaMetadata data = null;
370            if (isCurrentTrack) {
371                if (DEBUG) Log.d(TAG, "getAttrValue: item is active, using current data");
372                data = mediaController.getMetadata();
373                if (data == null)
374                    Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
375            }
376
377            if (data == null) {
378                // TODO: This code can be removed when b/63117921 is resolved
379                data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY);
380                extras = null; // We no longer need the data in here
381            }
382
383            extras = fillBundle(data, extras);
384
385            if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
386            switch (attr) {
387                case AvrcpConstants.ATTRID_TITLE:
388                    /* Title is mandatory attribute */
389                    if (isCurrentTrack) {
390                        attrValue = extras.getString(MediaMetadata.METADATA_KEY_TITLE);
391                    } else {
392                        attrValue = desc.getTitle().toString();
393                    }
394                    break;
395
396                case AvrcpConstants.ATTRID_ARTIST:
397                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
398                    break;
399
400                case AvrcpConstants.ATTRID_ALBUM:
401                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
402                    break;
403
404                case AvrcpConstants.ATTRID_TRACK_NUM:
405                    attrValue =
406                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
407                    break;
408
409                case AvrcpConstants.ATTRID_NUM_TRACKS:
410                    attrValue =
411                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
412                    break;
413
414                case AvrcpConstants.ATTRID_GENRE:
415                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
416                    break;
417
418                case AvrcpConstants.ATTRID_PLAY_TIME:
419                    attrValue = Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_DURATION));
420                    break;
421
422                case AvrcpConstants.ATTRID_COVER_ART:
423                    Log.e(TAG, "getAttrValue: Cover art attribute not supported");
424                    return null;
425
426                default:
427                    Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
428                    return null;
429            }
430        } catch (NullPointerException ex) {
431            Log.w(TAG, "getAttrValue: attr id not found in result");
432            /* checking if attribute is title, then it is mandatory and cannot send null */
433            if (attr == AvrcpConstants.ATTRID_TITLE) {
434                attrValue = "<Unknown Title>";
435            } else {
436                return null;
437            }
438        }
439        if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
440        return attrValue;
441    }
442
443    private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
444            MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController) {
445        /* Response parameters */
446        int[] attrIds = null; /* array of attr ids */
447        String[] attrValues = null; /* array of attr values */
448
449        /* variables to temperorily add attrs */
450        ArrayList<String> attrArray = new ArrayList<String>();
451        ArrayList<Integer> attrId = new ArrayList<Integer>();
452        ArrayList<Integer> attrTempId = new ArrayList<Integer>();
453
454        /* check if remote device has requested for attributes */
455        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
456            if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
457                for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
458                    attrTempId.add(idx); /* attr id 0x00 is unused */
459                }
460            } else {
461                /* get only the requested attribute ids from the request */
462                for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
463                    if (DEBUG)
464                        Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
465                                        + mItemAttrReqObj.mAttrIDs[idx]);
466                    attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
467                }
468            }
469        }
470
471        if (DEBUG) Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
472        /* lookup and copy values of attributes for ids requested above */
473        for (int idx = 0; idx < attrTempId.size(); idx++) {
474            /* check if media player provided requested attributes */
475            String value = getAttrValue(attrTempId.get(idx), mediaItem, mediaController);
476            if (value != null) {
477                attrArray.add(value);
478                attrId.add(attrTempId.get(idx));
479            }
480        }
481
482        /* copy filtered attr ids and attr values to response parameters */
483        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
484            attrIds = new int[attrId.size()];
485
486            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
487                attrIds[attrIndex] = attrId.get(attrIndex);
488
489            attrValues = attrArray.toArray(new String[attrId.size()]);
490
491            /* create rsp object and send response */
492            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
493            mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
494            return;
495        }
496    }
497
498    private long getActiveQueueItemId(@Nullable MediaController controller) {
499        if (controller == null) return MediaSession.QueueItem.UNKNOWN_ID;
500        PlaybackState state = controller.getPlaybackState();
501        if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
502                || state.getState() == PlaybackState.STATE_NONE)
503            return MediaSession.QueueItem.UNKNOWN_ID;
504        long qid = state.getActiveQueueItemId();
505        if (qid != MediaSession.QueueItem.UNKNOWN_ID) return qid;
506        // Check if we're presenting a "one item queue"
507        if (controller.getMetadata() != null) return SINGLE_QID;
508        return MediaSession.QueueItem.UNKNOWN_ID;
509    }
510
511    String displayMediaItem(MediaSession.QueueItem item) {
512        StringBuilder sb = new StringBuilder();
513        sb.append("#");
514        sb.append(item.getQueueId());
515        sb.append(": ");
516        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_TITLE, item, null)));
517        sb.append(" - ");
518        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ALBUM, item, null)));
519        sb.append(" by ");
520        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ARTIST, item, null)));
521        sb.append(" (");
522        sb.append(getAttrValue(AvrcpConstants.ATTRID_PLAY_TIME, item, null));
523        sb.append(" ");
524        sb.append(getAttrValue(AvrcpConstants.ATTRID_TRACK_NUM, item, null));
525        sb.append("/");
526        sb.append(getAttrValue(AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
527        sb.append(") ");
528        sb.append(getAttrValue(AvrcpConstants.ATTRID_GENRE, item, null));
529        return sb.toString();
530    }
531
532    public void dump(StringBuilder sb, @Nullable MediaController mediaController) {
533        ProfileService.println(sb, "AddressedPlayer info:");
534        ProfileService.println(sb, "mLastTrackIdSent: " + mLastTrackIdSent);
535        ProfileService.println(sb, "mNowPlayingList: " + mNowPlayingList.size() + " elements");
536        long currentQueueId = getActiveQueueItemId(mediaController);
537        for (MediaSession.QueueItem item : mNowPlayingList) {
538            long itemId = item.getQueueId();
539            ProfileService.println(
540                    sb, (itemId == currentQueueId ? "*" : " ") + displayMediaItem(item));
541        }
542    }
543}
544