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