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