• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface)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 
cleanup()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 */
getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj, @Nullable MediaController mediaController)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 */
getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr, @Nullable MediaController mediaController)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
updateNowPlayingList(@ullable MediaController mediaController)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 
sendNowPlayingListChanged()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 
fillBundle(MediaMetadata metadata, Bundle currentExtras)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 */
playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController)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 
getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController)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 
sendTrackChangeWithId(int type, @Nullable MediaController mediaController)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
getQueueSubset(@onNull List<MediaSession.QueueItem> items, long startItem, long endItem)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      */
getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj, @NonNull List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem, @NonNull MediaController mediaController)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 
getAttrValue(int attr, MediaSession.QueueItem item, @Nullable MediaController mediaController)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 
getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj, MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController)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 
getActiveQueueItemId(@ullable MediaController controller)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 
displayMediaItem(MediaSession.QueueItem item)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 
dump(StringBuilder sb, @Nullable MediaController mediaController)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