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