AddressedMediaPlayer.java revision d996f17ea97b2c592338bcfbc93056d0224da1cd
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.bluetooth.BluetoothAvrcp;
20import android.media.session.MediaSession;
21import android.media.session.MediaSession.QueueItem;
22import android.media.MediaDescription;
23import android.media.MediaMetadata;
24import android.os.Bundle;
25import android.util.Log;
26
27import java.nio.ByteBuffer;
28import java.util.List;
29import java.util.Arrays;
30import java.util.ArrayList;
31
32/*************************************************************************************************
33 * Provides functionality required for Addressed Media Player, like Now Playing List related
34 * browsing commands, control commands to the current addressed player(playItem, play, pause, etc)
35 * Acts as an Interface to communicate with media controller APIs for NowPlayingItems.
36 ************************************************************************************************/
37
38public class AddressedMediaPlayer {
39    static private final String TAG = "AddressedMediaPlayer";
40    static private final Boolean DEBUG = false;
41
42    private AvrcpMediaRspInterface mMediaInterface;
43    private NowPlayingListManager mNowPlayingListManager = new NowPlayingListManager();
44
45    /* Now playing UID */
46    private static final byte[] NOW_PLAYING_UID = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
47                                                  (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
48
49    public AddressedMediaPlayer(AvrcpMediaRspInterface _mediaInterface) {
50        mMediaInterface = _mediaInterface;
51    }
52
53    void cleanup() {
54        if (DEBUG) Log.v(TAG, "cleanup");
55        mNowPlayingListManager = null;
56        mMediaInterface = null;
57    }
58
59    /* get now playing list from addressed player */
60    void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
61            MediaController mediaController) {
62        List<QueueItem> tempItems;
63        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
64        if (DEBUG) Log.v(TAG, "getFolderItemsNowPlaying");
65
66        if (mNowPlayingItems != null) {
67            // We already have the cached list sending the response to remote
68            if (DEBUG) Log.i(TAG, "sending cached now playing list");
69            /* Filter attributes from cached NowPlayingList and send response */
70            getFolderItemsFilterAttr(bdaddr, reqObj, mNowPlayingItems,
71                    AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, reqObj.mStartItem, reqObj.mEndItem);
72        } else if (mediaController == null) {
73            Log.e(TAG, "mediaController = null, sending internal error response");
74            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
75        } else {
76            // We don't have the cached list, fetching it from Media Controller
77            mNowPlayingItems = mediaController.getQueue();
78            if (mNowPlayingItems == null) {
79                Log.w(TAG, "Received Now playing list is null from: " +
80                        mediaController.getPackageName() + ", sending internal error response");
81                mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
82            } else {
83                mNowPlayingListManager.setNowPlayingList(mNowPlayingItems);
84                getFolderItemsFilterAttr(bdaddr, reqObj, mNowPlayingItems,
85                        AvrcpConstants.BTRC_SCOPE_NOW_PLAYING, reqObj.mStartItem,
86                        reqObj.mEndItem);
87            }
88        }
89    }
90
91    /* get item attributes for item in now playing list */
92    void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
93            MediaController mediaController) {
94        int status = AvrcpConstants.RSP_NO_ERROR;
95        int idx;
96        long mediaID = ByteBuffer.wrap(itemAttr.mUid).getLong();
97        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
98
99        /* checking if item attributes has been asked for now playing item or
100         * some other item with specific media id */
101        if (Arrays.equals(itemAttr.mUid, NOW_PLAYING_UID)) {
102            if (DEBUG) Log.d(TAG, "getItemAttr: Remote requests for now playing contents:");
103
104            // get the current playing song metadata and sending the queueitem.
105            if (mediaController != null) {
106                MediaMetadata metadata = mediaController.getMetadata();
107                if (metadata != null) {
108                    getItemAttrFilterAttr(bdaddr, itemAttr, getQueueItem(metadata));
109                } else {
110                    Log.e(TAG, "getItemAttr: metadata = null");
111                    status = AvrcpConstants.RSP_INTERNAL_ERR;
112                }
113            } else {
114                Log.e(TAG, "getItemAttr: mediaController = null");
115                status = AvrcpConstants.RSP_INTERNAL_ERR;
116            }
117        } else if (mNowPlayingItems != null) {
118            if(DEBUG) printByteArray("getItemAttr-UID", itemAttr.mUid);
119            for (idx = 0; idx < mNowPlayingItems.size(); idx++) {
120                if (mediaID == mNowPlayingItems.get(idx).getQueueId()) {
121                    getItemAttrFilterAttr(bdaddr, itemAttr, mNowPlayingItems.get(idx));
122                    break;
123                }
124            }
125            if (idx >= mNowPlayingItems.size()) {
126                Log.e(TAG, "getItemAttr: idx is more than now playing list: idx = " + idx
127                        + ", now playing list size = " + mNowPlayingItems.size());
128                status = AvrcpConstants.RSP_INV_ITEM;
129            }
130        } else {
131            Log.e(TAG, "getItemAttr: mNowPlayingItems is null!");
132            status = AvrcpConstants.RSP_INTERNAL_ERR;
133        }
134
135        // sending error status in case of error
136        if (status != AvrcpConstants.RSP_NO_ERROR) {
137            mMediaInterface.getItemAttrRsp(bdaddr, status, null);
138        }
139    }
140
141    private MediaSession.QueueItem getQueueItem(MediaMetadata metadata) {
142        MediaDescription.Builder builder = new MediaDescription.Builder();
143
144        // getting the media id
145        String mediaId = metadata.getDescription().getMediaId();
146        if (mediaId != null) {
147            builder.setMediaId(mediaId);
148            if(DEBUG) Log.d(TAG, "Item mediaId = " + mediaId);
149        }
150
151        // getting the title
152        if (metadata.getDescription().getTitle() != null) {
153            String title = metadata.getDescription().getTitle().toString();
154            builder.setTitle(title);
155            if(DEBUG) Log.d(TAG, "Item title = " + title);
156        }
157
158        // getting the metadata from the key-value pairs and filling to bundle
159        String artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
160        String album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
161        String track_num = metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER) + "";
162        String num_tracks = metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS) + "";
163        String genre = metadata.getString(MediaMetadata.METADATA_KEY_GENRE);
164        String duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION) + "";
165
166        Bundle bundle = fillBundle(artist, album, track_num, num_tracks, genre, duration);
167        builder.setExtras(bundle);
168
169        // building a queue item from the above metadata
170        MediaDescription desc = builder.build();
171        return new QueueItem((desc), ByteBuffer.wrap(NOW_PLAYING_UID).getLong());
172    }
173
174    private Bundle fillBundle(String artist, String album, String trackNum, String numTracks,
175            String genre, String playTime) {
176
177        Bundle bundle = new Bundle();
178
179        bundle.putString(MediaMetadata.METADATA_KEY_ARTIST, artist);
180        bundle.putString(MediaMetadata.METADATA_KEY_ALBUM, album);
181        bundle.putString(MediaMetadata.METADATA_KEY_GENRE, genre);
182        bundle.putString(MediaMetadata.METADATA_KEY_NUM_TRACKS, numTracks);
183        bundle.putString(MediaMetadata.METADATA_KEY_DURATION, playTime);
184        bundle.putString(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNum);
185        return bundle;
186    }
187
188    void updateNowPlayingList(List<MediaSession.QueueItem> queue){
189        mNowPlayingListManager.setNowPlayingList(queue);
190    }
191
192    /* Instructs media player to play particular media item */
193    void playItem(byte[] bdaddr, byte[] uid, byte scope, MediaController mediaController) {
194        long qid = ByteBuffer.wrap(uid).getLong();
195        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
196
197        if (mediaController != null) {
198            MediaController.TransportControls mediaControllerCntrl =
199                    mediaController.getTransportControls();
200            if (DEBUG) Log.d(TAG, "Sending playID");
201
202            if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
203                int idx;
204                /* find the queueId of the mediaId to play */
205                if (mNowPlayingItems != null) {
206                    for (idx = 0; idx < mNowPlayingItems.size(); idx++) {
207                        if (qid == mNowPlayingItems.get(idx).getQueueId()) {
208                            mediaControllerCntrl.skipToQueueItem(qid);
209                            break;
210                        }
211                    }
212                    /* if mediaId is not found in nowplaying list */
213                    if (idx >= mNowPlayingItems.size()) {
214                        Log.w(TAG, "item is not present in queue");
215                        mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
216                    }
217                } else {
218                    Log.w(TAG, "nowPlayingItems is null");
219                    mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
220                }
221            }
222            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
223        } else {
224            Log.e(TAG, "mediaController is null");
225            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
226        }
227    }
228
229    void getTotalNumOfItems(byte[] bdaddr, byte scope, MediaController mediaController) {
230        if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope);
231        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
232        if (mNowPlayingItems != null) {
233            // We already have the cached list sending the response to remote
234            mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0,
235                    mNowPlayingItems.size());
236        } else if (mediaController == null) {
237            Log.e(TAG, "mediaController is null");
238            mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
239                    AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
240        } else {
241            // We don't have the cached list, fetching it from Media Controller
242            mNowPlayingItems = mediaController.getQueue();
243            if (mNowPlayingItems == null) {
244                Log.e(TAG, "mNowPlayingItems is null");
245                mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
246                        AvrcpConstants.RSP_INV_ITEM, 0, 0);
247            } else {
248                mNowPlayingListManager.setNowPlayingList(mediaController.getQueue());
249                mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
250                        AvrcpConstants.RSP_NO_ERROR, 0, mNowPlayingItems.size());
251            }
252        }
253    }
254
255    void sendTrackChangeWithId(int trackChangedNT, long trackNumber,
256            MediaController mediaController) {
257        if (DEBUG) Log.d(TAG, "sendTrackChangeWithId");
258        try {
259            String mediaId = mediaController.getMetadata().getDescription().getMediaId();
260            long qid = 0;
261            List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
262            /* traverse now playing list for current playing item */
263            for (QueueItem qitem : mNowPlayingItems) {
264                if (qitem.getDescription().getMediaId().equals(mediaId)) {
265                    qid = qitem.getQueueId();
266                    if (DEBUG) Log.d(TAG, "sendTrackChangeWithId: Found matching qid= " + qid);
267                    break;
268                }
269            }
270            /* for any item associated with NowPlaying, uid is queueId */
271            byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
272            if (DEBUG) printByteArray("trackChangedRsp", uid);
273            mMediaInterface.trackChangedRsp(trackChangedNT, uid);
274        } catch (NullPointerException e) {
275            Log.w(TAG, "Null Pointer while getting current track Uid from media player");
276            sendTrackChangeRsp(trackChangedNT, trackNumber);
277        }
278    }
279
280    /*
281     * utility function to respond for track change when failed to get current track UID
282     * from media controller
283     */
284    private void sendTrackChangeRsp(int trackChangedNT, long trackNumber) {
285        byte[] track = new byte[AvrcpConstants.TRACK_ID_SIZE];
286        /* track is stored in big endian format */
287        for (int idx = 0; idx < AvrcpConstants.TRACK_ID_SIZE; ++idx) {
288            if (trackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
289                    && trackNumber == -1) {
290                 /* if no track is currently selected then return 0xFF in interim response */
291                track[idx] = AvrcpConstants.NO_TRACK_SELECTED;
292            } else {
293                /* if Browsing is not supported and a track is selected, then return 0x00 */
294                track[idx] = AvrcpConstants.TRACK_IS_SELECTED;
295            }
296        }
297        if (DEBUG) printByteArray("sendTrackChangeRsp", track);
298        mMediaInterface.trackChangedRsp(trackChangedNT, track);
299    }
300
301    /*
302     * helper method to check if startItem and endItem index is with range of
303     * MediaItem list. (Resultset containing all items in current path)
304     */
305    private List<MediaSession.QueueItem> checkIndexOutofBounds(byte[] bdaddr,
306            List<MediaSession.QueueItem> children, int startItem, int endItem) {
307        try {
308            List<MediaSession.QueueItem> childrenSubList =
309                children.subList(startItem, Math.min(children.size(), endItem + 1));
310            if (childrenSubList.isEmpty()) {
311                Log.i(TAG, "childrenSubList is empty.");
312                throw new IndexOutOfBoundsException();
313            }
314            return childrenSubList;
315        } catch (IndexOutOfBoundsException ex) {
316            Log.i(TAG, "Index out of bounds start item =" + startItem + " end item = "
317                    + Math.min(children.size(), endItem + 1));
318            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
319            return null;
320        } catch (IllegalArgumentException ex) {
321            Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
322            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
323            return null;
324        }
325    }
326
327    /*
328     * helper method to filter required attibutes before sending GetFolderItems
329     * response
330     */
331    private void getFolderItemsFilterAttr(byte[] bdaddr,
332            AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
333            List<MediaSession.QueueItem> children, byte scope, int startItem, int endItem) {
334        if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = "
335                + endItem);
336
337        List<MediaSession.QueueItem> result_items = new ArrayList<MediaSession.QueueItem>();
338
339        if (children != null) {
340            /* check for index out of bound errors */
341            if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem))
342                    == null) {
343                Log.w(TAG, "result_items is null.");
344                mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
345                return;
346            }
347            FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
348
349            /* variables to temperorily add attrs */
350            ArrayList<String> attrArray = new ArrayList<String>();
351            ArrayList<Integer> attrId = new ArrayList<Integer>();
352
353            for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) {
354                // get the queue id
355                long qid = result_items.get(itemIndex).getQueueId();
356                byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
357
358                // get the array of uid from 2d to array 1D array
359                for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
360                    folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
361                }
362
363                /* Set display name for current item */
364                folderDataNative.mDisplayNames[itemIndex] = result_items.get(itemIndex)
365                        .getDescription().getTitle().toString();
366
367                int maxAttributesRequested = 0;
368                boolean isAllAttribRequested = false;
369                /* check if remote requested for attributes */
370                if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
371                    int attrCnt = 0;
372
373                    /* add requested attr ids to a temp array */
374                    if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
375                        isAllAttribRequested = true;
376                        maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
377                    } else {
378                        /* get only the requested attribute ids from the request */
379                        maxAttributesRequested = mFolderItemsReqObj.mNumAttr;
380                    }
381
382                    /* lookup and copy values of attributes for ids requested above */
383                    for (int idx = 0; idx < maxAttributesRequested; idx++) {
384                        /* check if media player provided requested attributes */
385                        String value = null;
386
387                        int attribId = isAllAttribRequested ? (idx + 1)
388                                : mFolderItemsReqObj.mAttrIDs[idx];
389                        if (attribId >= AvrcpConstants.ATTRID_TITLE
390                                && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) {
391                            if ((value = getAttrValue(attribId, result_items, itemIndex))
392                                    != null) {
393                                attrArray.add(value);
394                                attrId.add(attribId);
395                                attrCnt++;
396                            }
397                        } else {
398                            Log.w(TAG, "invalid attributed id is requested: " + attribId);
399                        }
400                    }
401                     /* add num attr actually received from media player for a particular item */
402                    folderDataNative.mAttributesNum[itemIndex] = attrCnt;
403                }
404            }
405
406            /* copy filtered attr ids and attr values to response parameters */
407            if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
408                folderDataNative.mAttrIds = new int[attrId.size()];
409                for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
410                    folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
411                folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
412            }
413            for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++)
414                if (DEBUG) Log.d(TAG, "folderDataNative.mAttributesNum"
415                        + folderDataNative.mAttributesNum[attrIndex] + " attrIndex " + attrIndex);
416
417            /* create rsp object and send response to remote device */
418            FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR,
419                    Avrcp.sUIDCounter, scope, folderDataNative.mNumItems,
420                    folderDataNative.mFolderTypes, folderDataNative.mPlayable,
421                    folderDataNative.mItemTypes, folderDataNative.mItemUid,
422                    folderDataNative.mDisplayNames, folderDataNative.mAttributesNum,
423                    folderDataNative.mAttrIds, folderDataNative.mAttrValues);
424            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
425        } else {
426            Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
427            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
428            return;
429        }
430    }
431
432    private String getAttrValue(int attr, List<MediaSession.QueueItem> resultItems,
433            int itemIndex) {
434        String attrValue = null;
435        try {
436            switch (attr) {
437            /* Title is mandatory attribute */
438                case AvrcpConstants.ATTRID_TITLE:
439                    attrValue = resultItems.get(itemIndex).getDescription().getTitle().toString();
440                    break;
441
442                case AvrcpConstants.ATTRID_ARTIST:
443                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
444                            .getString(MediaMetadata.METADATA_KEY_ARTIST);
445                    break;
446
447                case AvrcpConstants.ATTRID_ALBUM:
448                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
449                            .getString(MediaMetadata.METADATA_KEY_ALBUM);
450                    break;
451
452                case AvrcpConstants.ATTRID_TRACK_NUM:
453                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
454                            .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
455                    break;
456
457                case AvrcpConstants.ATTRID_NUM_TRACKS:
458                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
459                            .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
460                    break;
461
462                case AvrcpConstants.ATTRID_GENRE:
463                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
464                            .getString(MediaMetadata.METADATA_KEY_GENRE);
465                    break;
466
467                case AvrcpConstants.ATTRID_PLAY_TIME:
468                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
469                            .getString(MediaMetadata.METADATA_KEY_DURATION);
470                    break;
471
472                default:
473                    Log.e(TAG, "Unknown attribute ID");
474            }
475        } catch (IndexOutOfBoundsException ex) {
476            Log.w(TAG, "getAttrValue: requested item index out of bounds");
477            return null;
478        } catch (NullPointerException ex) {
479            Log.w(TAG, "getAttrValue: attr id not found in result");
480            /* checking if attribute is title, then it is mandatory and cannot send null */
481            if (attr == AvrcpConstants.ATTRID_TITLE) {
482                return "<Unknown Title>";
483            }
484            return null;
485        }
486        if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
487        return attrValue;
488    }
489
490    private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
491            MediaSession.QueueItem mediaItem) {
492        /* Response parameters */
493        int[] attrIds = null; /* array of attr ids */
494        String[] attrValues = null; /* array of attr values */
495        int attrCounter = 0; /* num attributes for each item */
496        List<MediaSession.QueueItem> resultItems = new ArrayList<MediaSession.QueueItem>();
497        resultItems.add(mediaItem);
498        /* variables to temperorily add attrs */
499        ArrayList<String> attrArray = new ArrayList<String>();
500        ArrayList<Integer> attrId = new ArrayList<Integer>();
501
502        ArrayList<Integer> attrTempId = new ArrayList<Integer>();
503
504        /* check if remote device has requested for attributes */
505        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
506            if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
507                for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
508                    attrTempId.add(idx); /* attr id 0x00 is unused */
509                }
510            } else {
511                /* get only the requested attribute ids from the request */
512                for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
513                    if (DEBUG) Log.d(TAG, "getAttrValue: attr id[" + idx + "] :" +
514                        mItemAttrReqObj.mAttrIDs[idx]);
515                    attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
516                }
517            }
518
519            if (DEBUG) Log.d(TAG, "getAttrValue: attr id list size:" + attrTempId.size());
520            /* lookup and copy values of attributes for ids requested above */
521            for (int idx = 0; idx < attrTempId.size(); idx++) {
522                /* check if media player provided requested attributes */
523                String value = null;
524                if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) {
525                    attrArray.add(value);
526                    attrId.add(attrTempId.get(idx));
527                    attrCounter++;
528                }
529            }
530            attrTempId = null;
531        }
532
533        /* copy filtered attr ids and attr values to response parameters */
534        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
535            attrIds = new int[attrId.size()];
536
537            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
538                attrIds[attrIndex] = attrId.get(attrIndex);
539
540            attrValues = attrArray.toArray(new String[attrId.size()]);
541
542            /* create rsp object and send response */
543            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR,
544                    (byte)attrCounter, attrIds, attrValues);
545            mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
546            return;
547        }
548    }
549
550    void handlePassthroughCmd(int id, int keyState, byte[] bdAddr,
551            MediaController mediaController) {
552
553        if (mediaController != null) {
554            MediaController.TransportControls mediaControllerCntrl =
555                mediaController.getTransportControls();
556            if (DEBUG) Log.v(TAG, "handlePassthroughCmd - id:" + id + " keyState:" + keyState);
557            if (keyState == AvrcpConstants.KEY_STATE_PRESS) {
558                switch (id) {
559                    case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
560                        mediaControllerCntrl.rewind();
561                        break;
562                    case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
563                        mediaControllerCntrl.fastForward();
564                        break;
565                    case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
566                        mediaControllerCntrl.play();
567                        break;
568                    case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
569                        mediaControllerCntrl.pause();
570                        break;
571                    case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
572                        mediaControllerCntrl.stop();
573                        break;
574                    case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
575                        mediaControllerCntrl.skipToNext();
576                        break;
577                    case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
578                        mediaControllerCntrl.skipToPrevious();
579                        break;
580                    default:
581                        Log.w(TAG, "unknown id:" + id + " keyState:" + keyState);
582                }
583            } else {
584                Log.i(TAG, "ignoring the release event for id:" + id + " keyState:" + keyState);
585            }
586        } else {
587            Log.e(TAG, "Unable to handlePassthroughCmd, mediaController is null!");
588        }
589    }
590
591    private void printByteArray(String arrName, byte[] array) {
592        StringBuilder byteArray = new StringBuilder(arrName + ": 0x");
593
594        for (int idx = 0; idx < array.length; idx++) {
595            byteArray.append(String.format(" %02x", array[idx]));
596        }
597        Log.d(TAG, byteArray + "");
598    }
599
600}
601