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