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