• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MediaConnectionCallback(String packageName)90         MediaConnectionCallback(String packageName) {
91             this.mCallbackPackageName = packageName;
92         }
93 
setBrowser(MediaBrowser b)94         public void setBrowser(MediaBrowser b) {
95             mBrowser = b;
96         }
97 
98         @Override
onConnected()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
onConnectionFailed()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
onConnectionSuspended()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 
ItemAttribSubscriber(@onNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId)175         ItemAttribSubscriber(@NonNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId) {
176             mAttrReq = attrReq;
177             mMediaId = mediaId;
178         }
179 
180         @Override
onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children)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
onError(String id)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 */
getItemAttrFilterAttr(@onNull MediaBrowser.MediaItem mediaItem)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 */
BrowsedMediaPlayer(byte[] address, Context context, AvrcpMediaRspInterface mAvrcpMediaRspInterface)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. */
onBrowseConnect(String connectedPackage, MediaBrowser browser)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 
setBrowsed(String packageName, String cls)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 */
cleanup()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 
isPlayerConnected()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 */
changePath(byte[] folderUid, byte direction)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 
getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr)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 
getTotalNumOfItems(byte scope)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 
getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj)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 */
playItem(byte[] uid, byte scope)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      */
checkIndexOutofBounds(byte[] bdaddr, List<MediaBrowser.MediaItem> children, long startItem, long endItem)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      */
getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj, List<MediaBrowser.MediaItem> children, byte scope, long startItem, long endItem)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 
getAttrValue(int attr, MediaBrowser.MediaItem item)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 
getPackageName()757     public String getPackageName() {
758         return mPackageName;
759     }
760 
761     /* Helper methods */
762 
763     /* check if item is browsable Down*/
isBrowsableFolderDn(String uid)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*/
isBrowsableFolderUp()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 */
byteToString(byte[] byteArray)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 */
stringToByte(String mediaId)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 */
queueItem2MediaItem( List<MediaSession.QueueItem> tempItems)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 */
intToByteArray(int value)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