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