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