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