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