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