blob: ccd88884621a400c007e55c63b98f0b51e0f3417 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.avrcpcontroller;
import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.service.media.MediaBrowserService;
import android.util.Log;
import android.util.Pair;
import com.android.bluetooth.R;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implements the MediaBrowserService interface to AVRCP and A2DP
*
* This service provides a means for external applications to access A2DP and AVRCP.
* The applications are expected to use MediaBrowser (see API) and all the music
* browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
*
* The current behavior of MediaSession exposed by this service is as follows:
* 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
* connected and first starts playing. Before it starts playing we do not active the session.
* 1.1 The session is active throughout the duration of connection.
* 2. The session is de-activated when the device disconnects. It will be connected again when (1)
* happens.
*/
public class BluetoothMediaBrowserService extends MediaBrowserService {
private static final String TAG = "BluetoothMediaBrowserService";
private static final boolean DBG = false;
private static final boolean VDBG = false;
private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
private static final float PLAYBACK_SPEED = 1.0f;
// Message sent when A2DP device is disconnected.
private static final int MSG_DEVICE_DISCONNECT = 0;
// Message sent when A2DP device is connected.
private static final int MSG_DEVICE_CONNECT = 2;
// Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
private static final int MSG_TRACK = 4;
// Internal message sent to trigger a AVRCP action.
private static final int MSG_AVRCP_PASSTHRU = 5;
// Internal message to trigger a getplaystatus command to remote.
private static final int MSG_AVRCP_GET_PLAY_STATUS_NATIVE = 6;
// Message sent when AVRCP browse is connected.
private static final int MSG_DEVICE_BROWSE_CONNECT = 7;
// Message sent when AVRCP browse is disconnected.
private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
// Message sent when folder list is fetched.
private static final int MSG_FOLDER_LIST = 9;
// Custom actions for PTS testing.
private static final String CUSTOM_ACTION_VOL_UP =
"com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_UP";
private static final String CUSTOM_ACTION_VOL_DN =
"com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_DN";
private static final String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
"com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
private MediaSession mSession;
private MediaMetadata mA2dpMetadata;
private AvrcpControllerService mAvrcpCtrlSrvc;
private boolean mBrowseConnected = false;
private BluetoothDevice mA2dpDevice = null;
private A2dpSinkService mA2dpSinkService = null;
private Handler mAvrcpCommandQueue;
private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
private int mCurrentlyHeldKey = 0;
// Browsing related structures.
private List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>();
private static final class AvrcpCommandQueueHandler extends Handler {
WeakReference<BluetoothMediaBrowserService> mInst;
AvrcpCommandQueueHandler(Looper looper, BluetoothMediaBrowserService sink) {
super(looper);
mInst = new WeakReference<BluetoothMediaBrowserService>(sink);
}
@Override
public void handleMessage(Message msg) {
BluetoothMediaBrowserService inst = mInst.get();
if (inst == null) {
Log.e(TAG, "Parent class has died; aborting.");
return;
}
switch (msg.what) {
case MSG_DEVICE_CONNECT:
inst.msgDeviceConnect((BluetoothDevice) msg.obj);
break;
case MSG_DEVICE_DISCONNECT:
inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
break;
case MSG_TRACK:
Pair<PlaybackState, MediaMetadata> pair =
(Pair<PlaybackState, MediaMetadata>) (msg.obj);
inst.msgTrack(pair.first, pair.second);
break;
case MSG_AVRCP_PASSTHRU:
inst.msgPassThru((int) msg.obj);
break;
case MSG_AVRCP_GET_PLAY_STATUS_NATIVE:
inst.msgGetPlayStatusNative();
break;
case MSG_DEVICE_BROWSE_CONNECT:
inst.msgDeviceBrowseConnect((BluetoothDevice) msg.obj);
break;
case MSG_DEVICE_BROWSE_DISCONNECT:
inst.msgDeviceBrowseDisconnect((BluetoothDevice) msg.obj);
break;
case MSG_FOLDER_LIST:
inst.msgFolderList((Intent) msg.obj);
break;
default:
Log.e(TAG, "Message not handled " + msg);
}
}
}
@Override
public void onCreate() {
if (DBG) Log.d(TAG, "onCreate");
super.onCreate();
mSession = new MediaSession(this, TAG);
setSessionToken(mSession.getSessionToken());
mSession.setCallback(mSessionCallbacks);
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
mSession.setQueue(mMediaQueue);
mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
refreshInitialPlayingState();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
registerReceiver(mBtReceiver, filter);
synchronized (this) {
mParentIdToRequestMap.clear();
}
setBluetoothMediaBrowserService(this);
}
@Override
public void onDestroy() {
if (DBG) Log.d(TAG, "onDestroy");
setBluetoothMediaBrowserService(null);
mSession.release();
unregisterReceiver(mBtReceiver);
super.onDestroy();
}
/**
* getBluetoothMediaBrowserService()
* Routine to get direct access to MediaBrowserService from within the same process.
*/
public static synchronized BluetoothMediaBrowserService getBluetoothMediaBrowserService() {
if (sBluetoothMediaBrowserService == null) {
Log.w(TAG, "getBluetoothMediaBrowserService(): service is NULL");
return null;
}
if (DBG) {
Log.d(TAG, "getBluetoothMediaBrowserService(): returning "
+ sBluetoothMediaBrowserService);
}
return sBluetoothMediaBrowserService;
}
private static synchronized void setBluetoothMediaBrowserService(
BluetoothMediaBrowserService instance) {
if (DBG) Log.d(TAG, "setBluetoothMediaBrowserService(): set to: " + instance);
sBluetoothMediaBrowserService = instance;
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
if (DBG) Log.d(TAG, "onGetRoot");
return new BrowserRoot(BrowseTree.ROOT, null);
}
@Override
public synchronized void onLoadChildren(final String parentMediaId,
final Result<List<MediaItem>> result) {
if (mAvrcpCtrlSrvc == null) {
Log.w(TAG, "AVRCP not yet connected.");
result.sendResult(Collections.emptyList());
return;
}
if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
List<MediaItem> contents = mAvrcpCtrlSrvc.getContents(mA2dpDevice, parentMediaId);
if (contents == null) {
mParentIdToRequestMap.put(parentMediaId, result);
result.detach();
} else {
result.sendResult(contents);
}
return;
}
@Override
public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
}
// Media Session Stuff.
private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
@Override
public void onPlay() {
if (DBG) Log.d(TAG, "onPlay");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@Override
public void onPause() {
if (DBG) Log.d(TAG, "onPause");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@Override
public void onSkipToNext() {
if (DBG) Log.d(TAG, "onSkipToNext");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@Override
public void onSkipToPrevious() {
if (DBG) Log.d(TAG, "onSkipToPrevious");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@Override
public void onSkipToQueueItem(long id) {
if (DBG) Log.d(TAG, "onSkipToQueueItem" + id);
MediaSession.QueueItem queueItem = mMediaQueue.get((int) id);
if (queueItem != null) {
String mediaId = queueItem.getDescription().getMediaId();
mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
}
}
@Override
public void onStop() {
if (DBG) Log.d(TAG, "onStop");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_STOP).sendToTarget();
}
@Override
public void onPrepare() {
if (DBG) Log.d(TAG, "onPrepare");
if (mA2dpSinkService != null) {
mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
}
}
@Override
public void onRewind() {
if (DBG) Log.d(TAG, "onRewind");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_REWIND).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@Override
public void onFastForward() {
if (DBG) Log.d(TAG, "onFastForward");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_FF).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
synchronized (BluetoothMediaBrowserService.this) {
// Play the item if possible.
mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
}
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
// Support VOL UP and VOL DOWN events for PTS testing.
@Override
public void onCustomAction(String action, Bundle extras) {
if (DBG) Log.d(TAG, "onCustomAction " + action);
if (CUSTOM_ACTION_VOL_UP.equals(action)) {
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_VOL_UP).sendToTarget();
} else if (CUSTOM_ACTION_VOL_DN.equals(action)) {
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_VOL_DOWN).sendToTarget();
} else if (CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE.equals(action)) {
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_GET_PLAY_STATUS_NATIVE).sendToTarget();
} else {
Log.w(TAG, "Custom action " + action + " not supported.");
}
}
};
private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DBG) Log.d(TAG, "onReceive intent=" + intent);
String action = intent.getAction();
BluetoothDevice btDev =
(BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
if (BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
if (DBG) {
Log.d(TAG, "handleConnectionStateChange: newState="
+ state + " btDev=" + btDev);
}
// Connected state will be handled when AVRCP BluetoothProfile gets connected.
if (state == BluetoothProfile.STATE_CONNECTED) {
mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_CONNECT, btDev).sendToTarget();
} else if (state == BluetoothProfile.STATE_DISCONNECTED) {
// Set the playback state to unconnected.
mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
// If we have been pushing updates via the session then stop sending them since
// we are not connected anymore.
if (mSession.isActive()) {
mSession.setActive(false);
}
}
} else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
action)) {
if (state == BluetoothProfile.STATE_CONNECTED) {
mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_CONNECT, btDev)
.sendToTarget();
} else if (state == BluetoothProfile.STATE_DISCONNECTED) {
mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_DISCONNECT, btDev)
.sendToTarget();
}
} else if (AvrcpControllerService.ACTION_TRACK_EVENT.equals(action)) {
PlaybackState pbb =
intent.getParcelableExtra(AvrcpControllerService.EXTRA_PLAYBACK);
MediaMetadata mmd =
intent.getParcelableExtra(AvrcpControllerService.EXTRA_METADATA);
mAvrcpCommandQueue.obtainMessage(MSG_TRACK,
new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
} else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
}
}
};
private synchronized void msgDeviceConnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "msgDeviceConnect");
// We are connected to a new device via A2DP now.
mA2dpDevice = device;
mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
if (mAvrcpCtrlSrvc == null) {
Log.e(TAG, "!!!AVRCP Controller cannot be null");
return;
}
refreshInitialPlayingState();
}
// Refresh the UI if we have a connected device and AVRCP is initialized.
private synchronized void refreshInitialPlayingState() {
if (mA2dpDevice == null) {
if (DBG) Log.d(TAG, "device " + mA2dpDevice);
return;
}
List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
if (devices.size() == 0) {
Log.w(TAG, "No devices connected yet");
return;
}
if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
return;
}
mA2dpDevice = devices.get(0);
mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
if (VDBG) {
Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
}
mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
mSession.setPlaybackState(playbackState);
}
private void msgDeviceDisconnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "msgDeviceDisconnect");
if (mA2dpDevice == null) {
Log.w(TAG, "Already disconnected - nothing to do here.");
return;
} else if (!mA2dpDevice.equals(device)) {
Log.e(TAG,
"Not the right device to disconnect current " + mA2dpDevice + " dc " + device);
return;
}
// Unset the session.
PlaybackState.Builder pbb = new PlaybackState.Builder();
pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
PLAYBACK_SPEED)
.setErrorMessage(getString(R.string.bluetooth_disconnected));
mSession.setPlaybackState(pbb.build());
// Set device to null.
mA2dpDevice = null;
mBrowseConnected = false;
// update playerList.
mMediaQueue.clear();
mSession.setQueue(mMediaQueue);
notifyChildrenChanged("__ROOT__");
}
private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
if (VDBG) Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + mmd);
// Log the current track position/content.
MediaController controller = mSession.getController();
PlaybackState prevPS = controller.getPlaybackState();
MediaMetadata prevMM = controller.getMetadata();
if (prevPS != null) {
Log.d(TAG, "prevPS " + prevPS);
}
if (prevMM != null) {
String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
if (VDBG) Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
}
if (mmd != null) {
if (VDBG) Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
mSession.setMetadata(mmd);
}
if (pb != null) {
if (DBG) Log.d(TAG, "msgTrack() playbackstate " + pb);
mSession.setPlaybackState(pb);
// If we are now playing then we should start pushing updates via MediaSession so that
// external UI (such as SystemUI) can show the currently playing music.
if (pb.getState() == PlaybackState.STATE_PLAYING && !mSession.isActive()) {
mSession.setActive(true);
}
}
}
private boolean isHoldableKey(int cmd) {
return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
|| (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
}
private synchronized void msgPassThru(int cmd) {
if (DBG) Log.d(TAG, "msgPassThru " + cmd);
if (mA2dpDevice == null) {
// We should have already disconnected - ignore this message.
Log.w(TAG, "Already disconnected ignoring.");
return;
}
// Some keys should be held until the next event.
if (mCurrentlyHeldKey != 0) {
mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, mCurrentlyHeldKey,
AvrcpControllerService.KEY_STATE_RELEASED);
if (mCurrentlyHeldKey == cmd) {
// Return to prevent starting FF/FR operation again
mCurrentlyHeldKey = 0;
return;
} else {
// FF/FR is in progress and other operation is desired
// so after stopping FF/FR, not returning so that command
// can be sent for the desired operation.
mCurrentlyHeldKey = 0;
}
}
// Send the pass through.
mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
AvrcpControllerService.KEY_STATE_PRESSED);
if (isHoldableKey(cmd)) {
// Release cmd next time a command is sent.
mCurrentlyHeldKey = cmd;
} else {
mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
AvrcpControllerService.KEY_STATE_RELEASED);
}
}
private synchronized void msgGetPlayStatusNative() {
if (DBG) Log.d(TAG, "msgGetPlayStatusNative");
if (mA2dpDevice == null) {
// We should have already disconnected - ignore this message.
Log.w(TAG, "Already disconnected ignoring.");
return;
}
// Ask for a non cached version.
mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice, false);
}
private void msgDeviceBrowseConnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "msgDeviceBrowseConnect device " + device);
// We should already be connected to this device over A2DP.
if (!device.equals(mA2dpDevice)) {
Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice + " browse "
+ device);
return;
}
mBrowseConnected = true;
// update playerList
notifyChildrenChanged("__ROOT__");
}
private void msgFolderList(Intent intent) {
// Parse the folder list for children list and id.
String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
updateNowPlayingQueue();
if (VDBG) Log.d(TAG, "Parent: " + id);
synchronized (this) {
// If we have a result object then we should send the result back
// to client since it is blocking otherwise we may have gotten more items
// from remote device, hence let client know to fetch again.
Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
if (results == null) {
Log.w(TAG, "Request no longer exists, notifying that children changed.");
notifyChildrenChanged(id);
} else {
List<MediaItem> folderList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, id);
results.sendResult(folderList);
}
}
}
private void updateNowPlayingQueue() {
List<MediaItem> songList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, "NOW_PLAYING");
Log.d(TAG, "NowPlaying" + songList.size());
mMediaQueue.clear();
for (MediaItem song : songList) {
mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(), mMediaQueue.size()));
}
mSession.setQueue(mMediaQueue);
}
/**
* processInternalEvent(Intent intent)
* Routine to provide MediaBrowserService with content updates from within the same process.
*/
public void processInternalEvent(Intent intent) {
String action = intent.getAction();
if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
}
}
private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
// Disconnect only if mA2dpDevice is non null
if (!device.equals(mA2dpDevice)) {
Log.w(TAG, "Browse disconnecting from different device a2dp " + mA2dpDevice + " browse "
+ device);
return;
}
mBrowseConnected = false;
}
}