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