Home | History | Annotate | Download | only in mbs
      1 /*
      2  * Copyright (C) 2015 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.a2dpsink.mbs;
     18 
     19 import android.bluetooth.BluetoothAvrcpController;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothProfile;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.media.MediaMetadata;
     27 import android.media.browse.MediaBrowser;
     28 import android.media.browse.MediaBrowser.MediaItem;
     29 import android.media.session.MediaController;
     30 import android.media.session.MediaSession;
     31 import android.media.session.PlaybackState;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.Looper;
     35 import android.os.Message;
     36 import android.os.Parcelable;
     37 import android.service.media.MediaBrowserService;
     38 import android.util.Log;
     39 import android.util.Pair;
     40 
     41 import com.android.bluetooth.R;
     42 import com.android.bluetooth.a2dpsink.A2dpSinkService;
     43 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
     44 import com.android.bluetooth.avrcpcontroller.BrowseTree;
     45 
     46 import java.lang.ref.WeakReference;
     47 import java.util.ArrayList;
     48 import java.util.Collections;
     49 import java.util.HashMap;
     50 import java.util.List;
     51 import java.util.Map;
     52 
     53 /**
     54  * Implements the MediaBrowserService interface to AVRCP and A2DP
     55  *
     56  * This service provides a means for external applications to access A2DP and AVRCP.
     57  * The applications are expected to use MediaBrowser (see API) and all the music
     58  * browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
     59  *
     60  * The current behavior of MediaSession exposed by this service is as follows:
     61  * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
     62  * connected and first starts playing. Before it starts playing we do not active the session.
     63  * 1.1 The session is active throughout the duration of connection.
     64  * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
     65  * happens.
     66  */
     67 public class A2dpMediaBrowserService extends MediaBrowserService {
     68     private static final String TAG = "A2dpMediaBrowserService";
     69     private static final boolean DBG = false;
     70     private static final boolean VDBG = false;
     71 
     72     private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
     73     private static final float PLAYBACK_SPEED = 1.0f;
     74 
     75     // Message sent when A2DP device is disconnected.
     76     private static final int MSG_DEVICE_DISCONNECT = 0;
     77     // Message sent when A2DP device is connected.
     78     private static final int MSG_DEVICE_CONNECT = 2;
     79     // Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
     80     private static final int MSG_TRACK = 4;
     81     // Internal message sent to trigger a AVRCP action.
     82     private static final int MSG_AVRCP_PASSTHRU = 5;
     83     // Internal message to trigger a getplaystatus command to remote.
     84     private static final int MSG_AVRCP_GET_PLAY_STATUS_NATIVE = 6;
     85     // Message sent when AVRCP browse is connected.
     86     private static final int MSG_DEVICE_BROWSE_CONNECT = 7;
     87     // Message sent when AVRCP browse is disconnected.
     88     private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
     89     // Message sent when folder list is fetched.
     90     private static final int MSG_FOLDER_LIST = 9;
     91 
     92     // Custom actions for PTS testing.
     93     private static final String CUSTOM_ACTION_VOL_UP =
     94             "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_UP";
     95     private static final String CUSTOM_ACTION_VOL_DN =
     96             "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_DN";
     97     private static final String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
     98             "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
     99 
    100     private MediaSession mSession;
    101     private MediaMetadata mA2dpMetadata;
    102 
    103     private AvrcpControllerService mAvrcpCtrlSrvc;
    104     private boolean mBrowseConnected = false;
    105     private BluetoothDevice mA2dpDevice = null;
    106     private A2dpSinkService mA2dpSinkService = null;
    107     private Handler mAvrcpCommandQueue;
    108     private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
    109 
    110     // Browsing related structures.
    111     private List<MediaItem> mNowPlayingList = null;
    112 
    113     private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
    114             | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
    115 
    116     private static final class AvrcpCommandQueueHandler extends Handler {
    117         WeakReference<A2dpMediaBrowserService> mInst;
    118 
    119         AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
    120             super(looper);
    121             mInst = new WeakReference<A2dpMediaBrowserService>(sink);
    122         }
    123 
    124         @Override
    125         public void handleMessage(Message msg) {
    126             A2dpMediaBrowserService inst = mInst.get();
    127             if (inst == null) {
    128                 Log.e(TAG, "Parent class has died; aborting.");
    129                 return;
    130             }
    131 
    132             switch (msg.what) {
    133                 case MSG_DEVICE_CONNECT:
    134                     inst.msgDeviceConnect((BluetoothDevice) msg.obj);
    135                     break;
    136                 case MSG_DEVICE_DISCONNECT:
    137                     inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
    138                     break;
    139                 case MSG_TRACK:
    140                     Pair<PlaybackState, MediaMetadata> pair =
    141                             (Pair<PlaybackState, MediaMetadata>) (msg.obj);
    142                     inst.msgTrack(pair.first, pair.second);
    143                     break;
    144                 case MSG_AVRCP_PASSTHRU:
    145                     inst.msgPassThru((int) msg.obj);
    146                     break;
    147                 case MSG_AVRCP_GET_PLAY_STATUS_NATIVE:
    148                     inst.msgGetPlayStatusNative();
    149                     break;
    150                 case MSG_DEVICE_BROWSE_CONNECT:
    151                     inst.msgDeviceBrowseConnect((BluetoothDevice) msg.obj);
    152                     break;
    153                 case MSG_DEVICE_BROWSE_DISCONNECT:
    154                     inst.msgDeviceBrowseDisconnect((BluetoothDevice) msg.obj);
    155                     break;
    156                 case MSG_FOLDER_LIST:
    157                     inst.msgFolderList((Intent) msg.obj);
    158                     break;
    159                 default:
    160                     Log.e(TAG, "Message not handled " + msg);
    161             }
    162         }
    163     }
    164 
    165     @Override
    166     public void onCreate() {
    167         if (DBG) Log.d(TAG, "onCreate");
    168         super.onCreate();
    169 
    170         mSession = new MediaSession(this, TAG);
    171         setSessionToken(mSession.getSessionToken());
    172         mSession.setCallback(mSessionCallbacks);
    173         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
    174                 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    175         mSession.setActive(true);
    176         mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
    177 
    178         refreshInitialPlayingState();
    179 
    180         IntentFilter filter = new IntentFilter();
    181         filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
    182         filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
    183         filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
    184         filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
    185         registerReceiver(mBtReceiver, filter);
    186 
    187         synchronized (this) {
    188             mParentIdToRequestMap.clear();
    189         }
    190     }
    191 
    192     @Override
    193     public void onDestroy() {
    194         if (DBG) Log.d(TAG, "onDestroy");
    195         mSession.release();
    196         unregisterReceiver(mBtReceiver);
    197         super.onDestroy();
    198     }
    199 
    200     @Override
    201     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
    202         return new BrowserRoot(BrowseTree.ROOT, null);
    203     }
    204 
    205     @Override
    206     public synchronized void onLoadChildren(final String parentMediaId,
    207             final Result<List<MediaItem>> result) {
    208         if (mAvrcpCtrlSrvc == null) {
    209             Log.w(TAG, "AVRCP not yet connected.");
    210             result.sendResult(Collections.emptyList());
    211             return;
    212         }
    213 
    214         if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
    215         if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) {
    216             result.sendResult(Collections.emptyList());
    217             return;
    218         }
    219 
    220         // Since we are using this thread from a binder thread we should make sure that
    221         // we synchronize against other such asynchronous calls.
    222         synchronized (this) {
    223             mParentIdToRequestMap.put(parentMediaId, result);
    224         }
    225         result.detach();
    226     }
    227 
    228     @Override
    229     public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
    230     }
    231 
    232     // Media Session Stuff.
    233     private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
    234         @Override
    235         public void onPlay() {
    236             if (DBG) Log.d(TAG, "onPlay");
    237             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    238                     AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
    239             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    240         }
    241 
    242         @Override
    243         public void onPause() {
    244             if (DBG) Log.d(TAG, "onPause");
    245             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    246                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
    247             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    248         }
    249 
    250         @Override
    251         public void onSkipToNext() {
    252             if (DBG) Log.d(TAG, "onSkipToNext");
    253             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    254                     AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD).sendToTarget();
    255             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    256         }
    257 
    258         @Override
    259         public void onSkipToPrevious() {
    260             if (DBG) Log.d(TAG, "onSkipToPrevious");
    261             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    262                     AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD).sendToTarget();
    263             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    264         }
    265 
    266         @Override
    267         public void onStop() {
    268             if (DBG) Log.d(TAG, "onStop");
    269             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    270                     AvrcpControllerService.PASS_THRU_CMD_ID_STOP).sendToTarget();
    271         }
    272 
    273         @Override
    274         public void onPrepare() {
    275             if (DBG) Log.d(TAG, "onPrepare");
    276             if (mA2dpSinkService != null) {
    277                 mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
    278             }
    279         }
    280 
    281         @Override
    282         public void onRewind() {
    283             if (DBG) Log.d(TAG, "onRewind");
    284             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    285                     AvrcpControllerService.PASS_THRU_CMD_ID_REWIND).sendToTarget();
    286             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    287         }
    288 
    289         @Override
    290         public void onFastForward() {
    291             if (DBG) Log.d(TAG, "onFastForward");
    292             mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    293                     AvrcpControllerService.PASS_THRU_CMD_ID_FF).sendToTarget();
    294             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    295         }
    296 
    297         @Override
    298         public void onPlayFromMediaId(String mediaId, Bundle extras) {
    299             synchronized (A2dpMediaBrowserService.this) {
    300                 // Play the item if possible.
    301                 mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
    302 
    303                 // Since we request explicit playback here we should start the updates to UI.
    304                 mAvrcpCtrlSrvc.startAvrcpUpdates();
    305             }
    306 
    307             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
    308         }
    309 
    310         // Support VOL UP and VOL DOWN events for PTS testing.
    311         @Override
    312         public void onCustomAction(String action, Bundle extras) {
    313             if (DBG) Log.d(TAG, "onCustomAction " + action);
    314             if (CUSTOM_ACTION_VOL_UP.equals(action)) {
    315                 mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    316                         AvrcpControllerService.PASS_THRU_CMD_ID_VOL_UP).sendToTarget();
    317             } else if (CUSTOM_ACTION_VOL_DN.equals(action)) {
    318                 mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
    319                         AvrcpControllerService.PASS_THRU_CMD_ID_VOL_DOWN).sendToTarget();
    320             } else if (CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE.equals(action)) {
    321                 mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_GET_PLAY_STATUS_NATIVE).sendToTarget();
    322             } else {
    323                 Log.w(TAG, "Custom action " + action + " not supported.");
    324             }
    325         }
    326     };
    327 
    328     private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
    329         @Override
    330         public void onReceive(Context context, Intent intent) {
    331             if (DBG) Log.d(TAG, "onReceive intent=" + intent);
    332             String action = intent.getAction();
    333             BluetoothDevice btDev =
    334                     (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    335             int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
    336 
    337             if (BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    338                 if (DBG) {
    339                     Log.d(TAG, "handleConnectionStateChange: newState="
    340                             + state + " btDev=" + btDev);
    341                 }
    342 
    343                 // Connected state will be handled when AVRCP BluetoothProfile gets connected.
    344                 if (state == BluetoothProfile.STATE_CONNECTED) {
    345                     mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_CONNECT, btDev).sendToTarget();
    346                 } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
    347                     // Set the playback state to unconnected.
    348                     mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
    349                     // If we have been pushing updates via the session then stop sending them since
    350                     // we are not connected anymore.
    351                     if (mSession.isActive()) {
    352                         mSession.setActive(false);
    353                     }
    354                 }
    355             } else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
    356                     action)) {
    357                 if (state == BluetoothProfile.STATE_CONNECTED) {
    358                     mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_CONNECT, btDev)
    359                             .sendToTarget();
    360                 } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
    361                     mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_DISCONNECT, btDev)
    362                             .sendToTarget();
    363                 }
    364             } else if (AvrcpControllerService.ACTION_TRACK_EVENT.equals(action)) {
    365                 PlaybackState pbb =
    366                         intent.getParcelableExtra(AvrcpControllerService.EXTRA_PLAYBACK);
    367                 MediaMetadata mmd =
    368                         intent.getParcelableExtra(AvrcpControllerService.EXTRA_METADATA);
    369                 mAvrcpCommandQueue.obtainMessage(MSG_TRACK,
    370                         new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
    371             } else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
    372                 mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
    373             }
    374         }
    375     };
    376 
    377     private synchronized void msgDeviceConnect(BluetoothDevice device) {
    378         if (DBG) Log.d(TAG, "msgDeviceConnect");
    379         // We are connected to a new device via A2DP now.
    380         mA2dpDevice = device;
    381         mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
    382         if (mAvrcpCtrlSrvc == null) {
    383             Log.e(TAG, "!!!AVRCP Controller cannot be null");
    384             return;
    385         }
    386         refreshInitialPlayingState();
    387     }
    388 
    389 
    390     // Refresh the UI if we have a connected device and AVRCP is initialized.
    391     private synchronized void refreshInitialPlayingState() {
    392         if (mA2dpDevice == null) {
    393             if (DBG) Log.d(TAG, "device " + mA2dpDevice);
    394             return;
    395         }
    396 
    397         List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
    398         if (devices.size() == 0) {
    399             Log.w(TAG, "No devices connected yet");
    400             return;
    401         }
    402 
    403         if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
    404             Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
    405             return;
    406         }
    407         mA2dpDevice = devices.get(0);
    408         mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
    409 
    410         PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
    411         // Add actions required for playback and rebuild the object.
    412         PlaybackState.Builder pbb = new PlaybackState.Builder(playbackState);
    413         playbackState = pbb.setActions(mTransportControlFlags).build();
    414 
    415         MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
    416         if (VDBG) {
    417             Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
    418         }
    419         mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
    420         mSession.setPlaybackState(playbackState);
    421     }
    422 
    423     private void msgDeviceDisconnect(BluetoothDevice device) {
    424         if (DBG) Log.d(TAG, "msgDeviceDisconnect");
    425         if (mA2dpDevice == null) {
    426             Log.w(TAG, "Already disconnected - nothing to do here.");
    427             return;
    428         } else if (!mA2dpDevice.equals(device)) {
    429             Log.e(TAG,
    430                     "Not the right device to disconnect current " + mA2dpDevice + " dc " + device);
    431             return;
    432         }
    433 
    434         // Unset the session.
    435         PlaybackState.Builder pbb = new PlaybackState.Builder();
    436         pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
    437                 PLAYBACK_SPEED)
    438                 .setActions(mTransportControlFlags)
    439                 .setErrorMessage(getString(R.string.bluetooth_disconnected));
    440         mSession.setPlaybackState(pbb.build());
    441 
    442         // Set device to null.
    443         mA2dpDevice = null;
    444         mBrowseConnected = false;
    445         // update playerList.
    446         notifyChildrenChanged("__ROOT__");
    447     }
    448 
    449     private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
    450         if (VDBG) Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + mmd);
    451         // Log the current track position/content.
    452         MediaController controller = mSession.getController();
    453         PlaybackState prevPS = controller.getPlaybackState();
    454         MediaMetadata prevMM = controller.getMetadata();
    455 
    456         if (prevPS != null) {
    457             Log.d(TAG, "prevPS " + prevPS);
    458         }
    459 
    460         if (prevMM != null) {
    461             String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
    462             long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
    463             if (VDBG) Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
    464         }
    465 
    466         if (mmd != null) {
    467             if (VDBG) Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
    468             mSession.setMetadata(mmd);
    469         }
    470 
    471         if (pb != null) {
    472             if (DBG) Log.d(TAG, "msgTrack() playbackstate " + pb);
    473             PlaybackState.Builder pbb = new PlaybackState.Builder(pb);
    474             pb = pbb.setActions(mTransportControlFlags).build();
    475             mSession.setPlaybackState(pb);
    476 
    477             // If we are now playing then we should start pushing updates via MediaSession so that
    478             // external UI (such as SystemUI) can show the currently playing music.
    479             if (pb.getState() == PlaybackState.STATE_PLAYING && !mSession.isActive()) {
    480                 mSession.setActive(true);
    481             }
    482         }
    483     }
    484 
    485     private synchronized void msgPassThru(int cmd) {
    486         if (DBG) Log.d(TAG, "msgPassThru " + cmd);
    487         if (mA2dpDevice == null) {
    488             // We should have already disconnected - ignore this message.
    489             Log.w(TAG, "Already disconnected ignoring.");
    490             return;
    491         }
    492 
    493         // Send the pass through.
    494         mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
    495                 AvrcpControllerService.KEY_STATE_PRESSED);
    496         mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
    497                 AvrcpControllerService.KEY_STATE_RELEASED);
    498     }
    499 
    500     private synchronized void msgGetPlayStatusNative() {
    501         if (DBG) Log.d(TAG, "msgGetPlayStatusNative");
    502         if (mA2dpDevice == null) {
    503             // We should have already disconnected - ignore this message.
    504             Log.w(TAG, "Already disconnected ignoring.");
    505             return;
    506         }
    507 
    508         // Ask for a non cached version.
    509         mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice, false);
    510     }
    511 
    512     private void msgDeviceBrowseConnect(BluetoothDevice device) {
    513         if (DBG) Log.d(TAG, "msgDeviceBrowseConnect device " + device);
    514         // We should already be connected to this device over A2DP.
    515         if (!device.equals(mA2dpDevice)) {
    516             Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice + " browse "
    517                     + device);
    518             return;
    519         }
    520         mBrowseConnected = true;
    521         // update playerList
    522         notifyChildrenChanged("__ROOT__");
    523     }
    524 
    525     private void msgFolderList(Intent intent) {
    526         // Parse the folder list for children list and id.
    527         List<Parcelable> extraParcelableList =
    528                 (ArrayList<Parcelable>) intent.getParcelableArrayListExtra(
    529                         AvrcpControllerService.EXTRA_FOLDER_LIST);
    530         List<MediaItem> folderList = new ArrayList<MediaItem>();
    531         for (Parcelable p : extraParcelableList) {
    532             folderList.add((MediaItem) p);
    533         }
    534 
    535         String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
    536         if (VDBG) Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
    537         synchronized (this) {
    538             // If we have a result object then we should send the result back
    539             // to client since it is blocking otherwise we may have gotten more items
    540             // from remote device, hence let client know to fetch again.
    541             Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
    542             if (results == null) {
    543                 Log.w(TAG, "Request no longer exists, notifying that children changed.");
    544                 notifyChildrenChanged(id);
    545             } else {
    546                 results.sendResult(folderList);
    547             }
    548         }
    549     }
    550 
    551     private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
    552         if (DBG) Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
    553         // Disconnect only if mA2dpDevice is non null
    554         if (!device.equals(mA2dpDevice)) {
    555             Log.w(TAG, "Browse disconnecting from different device a2dp " + mA2dpDevice + " browse "
    556                     + device);
    557             return;
    558         }
    559         mBrowseConnected = false;
    560     }
    561 }
    562