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