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.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