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