BrowsedMediaPlayer.java revision 417d1b696e6bcf1e22478b93e79aa5936537483a
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.content.ComponentName; 20import android.content.Context; 21import android.media.MediaDescription; 22import android.media.MediaMetadata; 23import android.media.browse.MediaBrowser; 24import android.media.browse.MediaBrowser.MediaItem; 25import android.media.session.MediaSession; 26import android.media.session.MediaSession.QueueItem; 27import android.util.Log; 28 29import java.math.BigInteger; 30import java.util.ArrayList; 31import java.util.HashMap; 32import java.util.List; 33import java.util.Stack; 34 35/************************************************************************************************* 36 * Provides functionality required for Browsed Media Player like browsing Virtual File System, get 37 * Item Attributes, play item from the file system, etc. 38 * Acts as an Interface to communicate with Media Browsing APIs for browsing FileSystem. 39 ************************************************************************************************/ 40 41class BrowsedMediaPlayer { 42 private static final boolean DEBUG = false; 43 private static final String TAG = "BrowsedMediaPlayer"; 44 45 /* connection state with MediaBrowseService */ 46 private static final int DISCONNECTED = 0; 47 private static final int CONNECTED = 1; 48 private static final int SUSPENDED = 2; 49 50 private static final String[] ROOT_FOLDER = {"root"}; 51 52 /* package and service name of target Media Player which is set for browsing */ 53 private String mPackageName; 54 private String mClassName; 55 private Context mContext; 56 private AvrcpMediaRspInterface mMediaInterface; 57 private byte[] mBDAddr; 58 59 /* Object used to connect to MediaBrowseService of Media Player */ 60 private MediaBrowser mMediaBrowser = null; 61 private MediaController mMediaController = null; 62 63 /* The mediaId to be used for subscribing for children using the MediaBrowser */ 64 private String mMediaId = null; 65 private String mRootFolderUid = null; 66 private int mConnState = DISCONNECTED; 67 68 /* stores the path trail during changePath */ 69 private Stack<String> mPathStack = null; 70 71 /* Number of items in current folder */ 72 private int mCurrFolderNumItems = 0; 73 74 /* store mapping between uid(Avrcp) and mediaId(Media Player). */ 75 private HashMap<Integer, String> mHmap = new HashMap<Integer, String>(); 76 77 /* command objects from avrcp handler */ 78 private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj; 79 80 private AvrcpCmd.ItemAttrCmd mItemAttrReqObj; 81 82 /* store result of getfolderitems with scope="vfs" */ 83 private List<MediaBrowser.MediaItem> mFolderItems = null; 84 85 /* Connection state callback handler */ 86 private MediaBrowser.ConnectionCallback browseMediaConnectionCallback = 87 new MediaBrowser.ConnectionCallback() { 88 89 @Override 90 public void onConnected() { 91 mConnState = CONNECTED; 92 if (DEBUG) Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName); 93 /* perform init tasks and set player as browsed player on successful connection */ 94 onBrowseConnect(); 95 } 96 97 @Override 98 public void onConnectionFailed() { 99 mConnState = DISCONNECTED; 100 Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName 101 + ", Sending fail response!"); 102 mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 103 (byte)0x00, 0, null); 104 } 105 106 @Override 107 public void onConnectionSuspended() { 108 mConnState = SUSPENDED; 109 Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName); 110 } 111 }; 112 113 /* Subscription callback handler. Subscribe to a folder to get its contents */ 114 private MediaBrowser.SubscriptionCallback folderItemsCb = 115 new MediaBrowser.SubscriptionCallback() { 116 117 @Override 118 public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { 119 if (DEBUG) Log.d(TAG, "OnChildren Loaded folder items: childrens= " + children.size()); 120 121 /* 122 * cache current folder items and send as rsp when remote requests 123 * get_folder_items (scope = vfs) 124 */ 125 if (mFolderItems == null) { 126 if (DEBUG) Log.d(TAG, "sending setbrowsed player rsp"); 127 mFolderItems = children; 128 mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 129 (byte)0x00, children.size(), ROOT_FOLDER); 130 } else { 131 mFolderItems = children; 132 mCurrFolderNumItems = mFolderItems.size(); 133 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 134 mCurrFolderNumItems); 135 } 136 mMediaBrowser.unsubscribe(parentId); 137 } 138 139 /* UID is invalid */ 140 @Override 141 public void onError(String id) { 142 Log.e(TAG, "set browsed player rsp. Could not get root folder items"); 143 mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 144 (byte)0x00, 0, null); 145 } 146 }; 147 148 /* callback from media player in response to getitemAttr request */ 149 private MediaBrowser.SubscriptionCallback itemAttrCb = 150 new MediaBrowser.SubscriptionCallback() { 151 @Override 152 public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { 153 if (DEBUG) Log.d(TAG, "itemAttrCb OnChildren Loaded"); 154 int status = AvrcpConstants.RSP_NO_ERROR; 155 156 if (children != null) { 157 boolean isChildrenFound = false; 158 /* some players may return all items in folder containing requested media item */ 159 for (int itemIndex = 0; itemIndex < children.size(); itemIndex++) { 160 if (children.get(itemIndex).getMediaId().equals(parentId)) { 161 if (DEBUG) Log.d(TAG, "found an item " + itemIndex + 162 children.get(itemIndex).getMediaId()); 163 getItemAttrFilterAttr(children.get(itemIndex)); 164 isChildrenFound = true; 165 break; 166 } 167 } 168 169 if (!isChildrenFound) { 170 Log.e(TAG, "not able to find the item:" + parentId); 171 status = AvrcpConstants.RSP_INV_ITEM; 172 } 173 } else { 174 Log.e(TAG, "children list is null for parent id:" + parentId); 175 status = AvrcpConstants.RSP_INV_ITEM; 176 } 177 // send only error from here, in case of success it will sent the attributes from getItemAttrFilterAttr 178 if (status != AvrcpConstants.RSP_NO_ERROR) { 179 /* send invalid uid rsp to remote device */ 180 mMediaInterface.getItemAttrRsp(mBDAddr, status, null); 181 } 182 } 183 @Override 184 public void onError(String id) { 185 Log.e(TAG, "Could not get attributes from media player. id: " + id); 186 mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); 187 } 188 }; 189 190 /* Constructor */ 191 public BrowsedMediaPlayer(byte[] address, Context context, 192 AvrcpMediaRspInterface mAvrcpMediaRspInterface) { 193 mContext = context; 194 mMediaInterface = mAvrcpMediaRspInterface; 195 mBDAddr = address; 196 } 197 198 /* initialize mediacontroller in order to communicate with media player. */ 199 private void onBrowseConnect() { 200 boolean isError = false; 201 MediaSession.Token token = null; 202 try { 203 /* get rootfolder uid from media player */ 204 if (mMediaId == null) { 205 mMediaId = mMediaBrowser.getRoot(); 206 /* 207 * assuming that root folder uid will not change on uids changed 208 */ 209 mRootFolderUid = mMediaId; 210 /* store root folder uid to stack */ 211 mPathStack.push(mMediaId); 212 } 213 214 if (!mMediaBrowser.isConnected()) { 215 isError = true; 216 Log.e(TAG, "setBrowsedPlayer : Not connected"); 217 } 218 219 if ((token = mMediaBrowser.getSessionToken()) == null) { 220 isError = true; 221 Log.e(TAG, "setBrowsedPlayer : No Session token"); 222 } 223 224 if (isError == false) { 225 mMediaController = MediaController.wrap( 226 new android.media.session.MediaController(mContext, token)); 227 /* get root folder items */ 228 mMediaBrowser.subscribe(mRootFolderUid, folderItemsCb); 229 } 230 } catch (NullPointerException ex) { 231 isError = true; 232 Log.e(TAG, "setBrowsedPlayer : Null pointer during init"); 233 ex.printStackTrace(); 234 } 235 236 if (isError) { 237 mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 238 (byte)0x00, 0, null); 239 } 240 } 241 242 public void setBrowsed(String packageName, String cls) { 243 mPackageName = packageName; 244 mClassName = cls; 245 /* cleanup variables from previous browsed calls */ 246 mFolderItems = null; 247 mMediaId = null; 248 mRootFolderUid = null; 249 /* 250 * create stack to store the navigation trail (current folder ID). This 251 * will be required while navigating up the folder 252 */ 253 mPathStack = new Stack<String>(); 254 /* Bind to MediaBrowseService of MediaPlayer */ 255 mMediaBrowser = new MediaBrowser(mContext, new ComponentName(mPackageName, mClassName), 256 browseMediaConnectionCallback, null); 257 connectToPlayer(); 258 } 259 260 /* called when connection to media player is closed */ 261 public void cleanup() { 262 if (DEBUG) 263 Log.d(TAG, "cleanup"); 264 if (mConnState != DISCONNECTED) { 265 disconnectFromPlayer(); 266 } 267 268 mHmap = null; 269 mMediaController = null; 270 mMediaBrowser = null; 271 } 272 273 private void connectToPlayer() { 274 if (DEBUG) Log.d(TAG, "connectToPlayer"); 275 mMediaBrowser.connect(); 276 } 277 278 public void disconnectFromPlayer() { 279 if (DEBUG) Log.d(TAG, "disconnectFromPlayer"); 280 mMediaBrowser.disconnect(); 281 } 282 283 public boolean isPlayerConnected() { 284 if (mMediaBrowser != null) { 285 return mMediaBrowser.isConnected(); 286 } else { 287 if (DEBUG) Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!"); 288 return false; 289 } 290 } 291 292 /* returns number of items in new path as reponse */ 293 public void changePath(byte[] folderUid, byte direction) { 294 if (DEBUG) Log.d(TAG, "changePath.direction = " + direction); 295 String newPath = ""; 296 297 if (isPlayerConnected() == false) { 298 Log.w(TAG, "changePath:disconnected from player service, sending internal error"); 299 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0); 300 return; 301 } 302 303 if (mMediaBrowser == null) { 304 Log.e(TAG, "mediaController is null, sending internal error"); 305 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0); 306 return; 307 } 308 309 /* check direction and change the path */ 310 if (direction == AvrcpConstants.DIR_DOWN) { /* move down */ 311 if ((newPath = byteToString(folderUid)) == null) { 312 Log.e(TAG, "Could not get media item from folder Uid, sending err response"); 313 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, 0); 314 } else if (isBrowsableFolderDn(newPath) == false) { 315 /* new path is not browsable */ 316 Log.e(TAG, "ItemUid received from changePath cmd is not browsable"); 317 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRECTORY, 0); 318 } else if (mPathStack.peek().equals(newPath) == true) { 319 /* new_folder is same as current folder */ 320 Log.e(TAG, "new_folder is same as current folder, Invalid direction!"); 321 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0); 322 } else { 323 mMediaBrowser.subscribe(newPath, folderItemsCb); 324 /* assume that call is success and update stack with new folder path */ 325 mPathStack.push(newPath); 326 } 327 } else if (direction == AvrcpConstants.DIR_UP) { /* move up */ 328 if (isBrowsableFolderUp() == false) { 329 /* Already on the root, cannot allow up: PTS: test case TC_TG_MCN_CB_BI_02_C 330 * This is required, otherwise some CT will keep on sending change path up 331 * until they receive error */ 332 Log.w(TAG, "Cannot go up from now, already in the root, Invalid direction!"); 333 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0); 334 } else { 335 /* move folder up */ 336 mPathStack.pop(); 337 newPath = mPathStack.peek(); 338 mMediaBrowser.subscribe(newPath, folderItemsCb); 339 } 340 } else { /* invalid direction */ 341 Log.w(TAG, "changePath : Invalid direction " + direction); 342 mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0); 343 } 344 } 345 346 public void getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) { 347 String mediaID; 348 if (DEBUG) Log.d(TAG, "getItemAttr"); 349 350 /* 351 * store request parameters for reference. To be used to filter 352 * attributes when sending response 353 */ 354 mItemAttrReqObj = itemAttr; 355 356 /* check if uid is valid by doing a lookup in hashmap */ 357 if ((mediaID = byteToString(itemAttr.mUid)) == null) { 358 Log.e(TAG, "uid is invalid"); 359 mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null); 360 return; 361 } 362 363 /* check scope */ 364 if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) { 365 if (mMediaBrowser != null) { 366 mMediaBrowser.subscribe(mediaID, itemAttrCb); 367 } else { 368 Log.e(TAG, "mMediaBrowser is null"); 369 mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); 370 } 371 } else { 372 Log.e(TAG, "invalid scope"); 373 mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, null); 374 } 375 } 376 377 public void getTotalNumOfItems(byte scope) { 378 if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope); 379 switch (scope) { 380 case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM: 381 if (mFolderItems != null) { 382 /* find num items using size of already cached folder items */ 383 mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, 384 AvrcpConstants.RSP_NO_ERROR, 0, mFolderItems.size()); 385 } else { 386 Log.e(TAG, "mFolderItems is null, sending internal error"); 387 /* folderitems were not fetched during change path */ 388 mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, 389 AvrcpConstants.RSP_INTERNAL_ERR, 0, 0); 390 } 391 break; 392 default: 393 Log.e(TAG, "getTotalNumOfItems error" + scope); 394 mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0); 395 break; 396 } 397 } 398 399 public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) { 400 if (isPlayerConnected()) { 401 if (DEBUG) Log.d(TAG, "getFolderItemsVFS"); 402 mFolderItemsReqObj = reqObj; 403 404 if (mFolderItems == null) { 405 /* Failed to fetch folder items from media player. Send error to remote device */ 406 Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS"); 407 mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); 408 } else { 409 /* Filter attributes based on the request and send response to remote device */ 410 getFolderItemsFilterAttr(mBDAddr, reqObj, mFolderItems, 411 AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, 412 mFolderItemsReqObj.mStartItem, mFolderItemsReqObj.mEndItem); 413 } 414 } else { 415 Log.e(TAG, "unable to connect to media player, sending internal error"); 416 /* unable to connect to media player. Send error response to remote device */ 417 mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); 418 } 419 } 420 421 /* Instructs media player to play particular media item */ 422 public void playItem(byte[] uid, byte scope) { 423 String folderUid; 424 425 if (isPlayerConnected()) { 426 /* check if uid is valid */ 427 if ((folderUid = byteToString(uid)) == null) { 428 Log.e(TAG, "uid is invalid!"); 429 mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM); 430 return; 431 } 432 433 if (mMediaController != null) { 434 MediaController.TransportControls mediaControllerCntrl = 435 mMediaController.getTransportControls(); 436 if (DEBUG) Log.d(TAG, "Sending playID: " + folderUid); 437 438 if (scope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) { 439 mediaControllerCntrl.playFromMediaId(folderUid, null); 440 mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR); 441 } else { 442 Log.e(TAG, "playItem received for invalid scope!"); 443 mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE); 444 } 445 } else { 446 Log.e(TAG, "mediaController is null"); 447 mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR); 448 } 449 } else { 450 Log.e(TAG, "playItem: Not connected to media player"); 451 mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR); 452 } 453 } 454 455 /* 456 * helper method to check if startItem and endItem index is with range of 457 * MediaItem list. (Resultset containing all items in current path) 458 */ 459 private List<MediaBrowser.MediaItem> checkIndexOutofBounds(byte[] bdaddr, 460 List<MediaBrowser.MediaItem> children, int startItem, int endItem) { 461 try { 462 List<MediaBrowser.MediaItem> childrenSubList = 463 children.subList(startItem, Math.min(children.size(), endItem + 1)); 464 if (childrenSubList.isEmpty()) { 465 Log.i(TAG, "childrenSubList is empty."); 466 throw new IndexOutOfBoundsException(); 467 } 468 return childrenSubList; 469 } catch (IndexOutOfBoundsException ex) { 470 Log.w(TAG, "Index out of bounds start item ="+ startItem + " end item = "+ 471 Math.min(children.size(), endItem + 1)); 472 mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); 473 return null; 474 } catch (IllegalArgumentException ex) { 475 Log.i(TAG, "Index out of bounds start item =" + startItem + " > size"); 476 mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); 477 return null; 478 } 479 } 480 481 482 /* 483 * helper method to filter required attibutes before sending GetFolderItems response 484 */ 485 public void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj, 486 List<MediaBrowser.MediaItem> children, byte scope, int startItem, int endItem) { 487 if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + 488 ", endItem = " + endItem); 489 490 List<MediaBrowser.MediaItem> result_items = new ArrayList<MediaBrowser.MediaItem>(); 491 492 if (children != null) { 493 /* check for index out of bound errors */ 494 if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem)) == null) { 495 Log.w(TAG, "result_items is null."); 496 mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); 497 return; 498 } 499 FolderItemsData folderDataNative = new FolderItemsData(result_items.size()); 500 501 /* variables to temperorily add attrs */ 502 ArrayList<String> attrArray = new ArrayList<String>(); 503 ArrayList<Integer> attrId = new ArrayList<Integer>(); 504 505 for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) { 506 /* item type. Needs to be set by media player */ 507 if ((result_items.get(itemIndex).getFlags() & 508 MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) { 509 folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER; 510 } else { 511 folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_MEDIA; 512 } 513 514 /* set playable */ 515 if ((result_items.get(itemIndex).getFlags() 516 & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) { 517 folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_PLAYABLE; 518 } else { 519 folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE; 520 } 521 /* set uid for current item */ 522 byte[] uid = 523 stringToByte(result_items.get(itemIndex).getDescription().getMediaId()); 524 for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) { 525 folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx]; 526 } 527 528 /* Set display name for current item */ 529 folderDataNative.mDisplayNames[itemIndex] = 530 result_items.get(itemIndex).getDescription().getTitle().toString(); 531 532 int maxAttributesRequested = 0; 533 boolean isAllAttribRequested = false; 534 /* check if remote requested for attributes */ 535 if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) { 536 int attrCnt = 0; 537 538 /* add requested attr ids to a temp array */ 539 if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) { 540 isAllAttribRequested = true; 541 maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR; 542 } else { 543 /* get only the requested attribute ids from the request */ 544 maxAttributesRequested = mFolderItemsReqObj.mNumAttr; 545 } 546 547 /* lookup and copy values of attributes for ids requested above */ 548 for (int idx = 0; idx < maxAttributesRequested; idx++) { 549 /* check if media player provided requested attributes */ 550 String value = null; 551 552 int attribId = isAllAttribRequested ? (idx + 1) : 553 mFolderItemsReqObj.mAttrIDs[idx]; 554 if(attribId >= AvrcpConstants.ATTRID_TITLE && 555 attribId <= AvrcpConstants.ATTRID_PLAY_TIME) { 556 if ((value = getAttrValue(attribId, result_items, 557 itemIndex)) != null) { 558 attrArray.add(value); 559 attrId.add(attribId); 560 attrCnt++; 561 } 562 } else { 563 Log.d(TAG, "invalid attributed id is requested: " + attribId); 564 } 565 } 566 /* add num attr actually received from media player for a particular item */ 567 folderDataNative.mAttributesNum[itemIndex] = attrCnt; 568 } 569 } 570 571 /* copy filtered attr ids and attr values to response parameters */ 572 if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) { 573 folderDataNative.mAttrIds = new int[attrId.size()]; 574 for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) 575 folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex); 576 folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]); 577 } 578 579 /* create rsp object and send response to remote device */ 580 FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, 581 Avrcp.sUIDCounter, scope, folderDataNative.mNumItems, 582 folderDataNative.mFolderTypes, folderDataNative.mPlayable, 583 folderDataNative.mItemTypes,folderDataNative.mItemUid, 584 folderDataNative.mDisplayNames, folderDataNative.mAttributesNum, 585 folderDataNative.mAttrIds, folderDataNative.mAttrValues); 586 mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj); 587 } else { 588 Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr"); 589 mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); 590 return; 591 } 592 } 593 594 public static String getAttrValue(int attr, List<MediaBrowser.MediaItem> resultItems, 595 int itemIndex) { 596 597 String attrValue = null; 598 try { 599 switch (attr) { 600 /* Title is mandatory attribute */ 601 case AvrcpConstants.ATTRID_TITLE: 602 attrValue = resultItems.get(itemIndex).getDescription().getTitle().toString(); 603 break; 604 case AvrcpConstants.ATTRID_ARTIST: 605 attrValue = resultItems.get(itemIndex).getDescription().getExtras() 606 .getString(MediaMetadata.METADATA_KEY_ARTIST); 607 break; 608 609 case AvrcpConstants.ATTRID_ALBUM: 610 attrValue = resultItems.get(itemIndex).getDescription().getExtras() 611 .getString(MediaMetadata.METADATA_KEY_ALBUM); 612 break; 613 614 case AvrcpConstants.ATTRID_TRACK_NUM: 615 attrValue = resultItems.get(itemIndex).getDescription().getExtras() 616 .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER); 617 break; 618 619 case AvrcpConstants.ATTRID_NUM_TRACKS: 620 attrValue = resultItems.get(itemIndex).getDescription().getExtras() 621 .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS); 622 break; 623 624 case AvrcpConstants.ATTRID_GENRE: 625 attrValue = resultItems.get(itemIndex).getDescription().getExtras() 626 .getString(MediaMetadata.METADATA_KEY_GENRE); 627 628 case AvrcpConstants.ATTRID_PLAY_TIME: 629 attrValue = resultItems.get(itemIndex).getDescription().getExtras() 630 .getString(MediaMetadata.METADATA_KEY_DURATION); 631 632 case AvrcpConstants.ATTRID_COVER_ART: 633 Log.e(TAG, "Cover art attribute not supported"); 634 break; 635 636 default: 637 Log.e(TAG, "Unknown attribute ID"); 638 } 639 } catch (IndexOutOfBoundsException ex) { 640 Log.w(TAG, "getAttrValue: requested item index out of bounds"); 641 return null; 642 } catch (NullPointerException ex) { 643 Log.w(TAG, "getAttrValue: attr id not found in result"); 644 /* checking if attribute is title, then it is mandatory and cannot send null */ 645 if (attr == AvrcpConstants.ATTRID_TITLE) { 646 return "<Unknown Title>"; 647 } 648 return null; 649 } 650 if(DEBUG) Log.d(TAG, "getAttrValue: attrvalue = "+ attrValue + "attr id:" + attr); 651 return attrValue; 652 } 653 654 /* helper method to filter required attibutes before sending getItemAttrdg response */ 655 private void getItemAttrFilterAttr(MediaBrowser.MediaItem mediaItem) { 656 /* Response parameters */ 657 int[] attrIds = null; /* array of attr ids */ 658 String[] attrValues = null; /* array of attr values */ 659 int attrCounter = 0; /* num attributes for each item */ 660 List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>(); 661 resultItems.add(mediaItem); 662 /* variables to temperorily add attrs */ 663 ArrayList<String> attrArray = new ArrayList<String>(); 664 ArrayList<Integer> attrId = new ArrayList<Integer>(); 665 666 if (mediaItem == null) { 667 Log.e(TAG, "getItemAttrFilterAttr: media item is null"); 668 mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); 669 return; 670 } 671 672 ArrayList<Integer> attrTempId = new ArrayList<Integer>(); 673 674 /* check if remote device has requested for attributes */ 675 if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) { 676 if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL || 677 mItemAttrReqObj.mNumAttr == AvrcpConstants.MAX_NUM_ATTR) { 678 for (int idx = 1; idx <= AvrcpConstants.MAX_NUM_ATTR; idx++) { 679 attrTempId.add(idx); /* attr id 0x00 is unused */ 680 } 681 } else { 682 /* get only the requested attribute ids from the request */ 683 for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) { 684 attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]); 685 } 686 } 687 688 /* lookup and copy values of attributes for ids requested above */ 689 for (int idx = 0; idx < attrTempId.size(); idx++) { 690 /* check if media player provided requested attributes */ 691 String value = null; 692 if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) { 693 attrArray.add(value); 694 attrId.add(attrTempId.get(idx)); 695 attrCounter++; 696 } 697 } 698 attrTempId = null; 699 } else { 700 Log.i(TAG, "getItemAttrFilterAttr: No attributes requested"); 701 } 702 703 /* copy filtered attr ids and attr values to response parameters */ 704 if (this.mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) { 705 attrIds = new int[attrId.size()]; 706 707 for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) 708 attrIds[attrIndex] = attrId.get(attrIndex); 709 710 attrValues = attrArray.toArray(new String[attrId.size()]); 711 712 /* create rsp object and send response */ 713 ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, 714 (byte)attrCounter, attrIds, attrValues); 715 mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, rspObj); 716 } 717 } 718 719 public String getPackageName() { 720 return mPackageName; 721 } 722 723 /* Helper methods */ 724 725 /* check if item is browsable Down*/ 726 private boolean isBrowsableFolderDn(String uid) { 727 for (MediaBrowser.MediaItem item : mFolderItems) { 728 if (item.getMediaId().equals(uid) && 729 ((item.getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE) == 730 MediaBrowser.MediaItem.FLAG_BROWSABLE)) 731 return true; 732 } 733 return false; 734 } 735 736 /* check if browsable Up*/ 737 private boolean isBrowsableFolderUp() { 738 if (mPathStack.peek().equals(mRootFolderUid)) { 739 /* Already on the root, cannot go up */ 740 return false; 741 } 742 return true; 743 } 744 745 /* convert uid to mediaId */ 746 private String byteToString(byte[] byteArray) { 747 int uid = new BigInteger(byteArray).intValue(); 748 String mediaId = mHmap.get(uid); 749 return mediaId; 750 } 751 752 /* convert mediaId to uid */ 753 private byte[] stringToByte(String mediaId) { 754 /* check if this mediaId already exists in hashmap */ 755 if (!mHmap.containsValue(mediaId)) { /* add to hashmap */ 756 // Offset by one as uid 0 is reserved 757 int uid = mHmap.size() + 1; 758 mHmap.put(uid, mediaId); 759 return intToByteArray(uid); 760 } else { /* search key for give mediaId */ 761 for (int uid : mHmap.keySet()) { 762 if (mHmap.get(uid).equals(mediaId)) { 763 return intToByteArray(uid); 764 } 765 } 766 } 767 return null; 768 } 769 770 /* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */ 771 private List<MediaBrowser.MediaItem> queueItem2MediaItem( 772 List<MediaSession.QueueItem> tempItems) { 773 774 List<MediaBrowser.MediaItem> tempMedia = new ArrayList<MediaBrowser.MediaItem>(); 775 for (int itemCount = 0; itemCount < tempItems.size(); itemCount++) { 776 MediaDescription.Builder build = new MediaDescription.Builder(); 777 build.setMediaId(Long.toString(tempItems.get(itemCount).getQueueId())); 778 build.setTitle(tempItems.get(itemCount).getDescription().getTitle()); 779 build.setExtras(tempItems.get(itemCount).getDescription().getExtras()); 780 MediaDescription des = build.build(); 781 MediaItem item = new MediaItem((des), MediaItem.FLAG_PLAYABLE); 782 tempMedia.add(item); 783 } 784 return tempMedia; 785 } 786 787 /* convert integer to byte array of size 8 bytes */ 788 public byte[] intToByteArray(int value) { 789 int index = 0; 790 byte[] encodedValue = new byte[AvrcpConstants.UID_SIZE]; 791 792 encodedValue[index++] = (byte)0x00; 793 encodedValue[index++] = (byte)0x00; 794 encodedValue[index++] = (byte)0x00; 795 encodedValue[index++] = (byte)0x00; 796 encodedValue[index++] = (byte)(value >> 24); 797 encodedValue[index++] = (byte)(value >> 16); 798 encodedValue[index++] = (byte)(value >> 8); 799 encodedValue[index++] = (byte)value; 800 801 return encodedValue; 802 } 803} 804