• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink)119         AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
120             super(looper);
121             mInst = new WeakReference<A2dpMediaBrowserService>(sink);
122         }
123 
124         @Override
handleMessage(Message msg)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
onCreate()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
onDestroy()193     public void onDestroy() {
194         if (DBG) Log.d(TAG, "onDestroy");
195         mSession.release();
196         unregisterReceiver(mBtReceiver);
197         super.onDestroy();
198     }
199 
200     @Override
onGetRoot(String clientPackageName, int clientUid, Bundle rootHints)201     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
202         return new BrowserRoot(BrowseTree.ROOT, null);
203     }
204 
205     @Override
onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result)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
onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result)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 
msgDeviceConnect(BluetoothDevice device)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.
refreshInitialPlayingState()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 
msgDeviceDisconnect(BluetoothDevice device)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 
msgTrack(PlaybackState pb, MediaMetadata mmd)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 
msgPassThru(int cmd)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 
msgGetPlayStatusNative()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 
msgDeviceBrowseConnect(BluetoothDevice device)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 
msgFolderList(Intent intent)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 
msgDeviceBrowseDisconnect(BluetoothDevice device)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