Merge "Add a new state machine for handling the incoming / outgoing profile connections." into kraken
This commit is contained in:
committed by
Android (Google) Code Review
commit
e50ab5f94c
@@ -395,7 +395,6 @@ public final class BluetoothHeadset {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if current platform supports voice dialing over bluetooth SCO.
|
||||
* @return true if voice dialing over bluetooth is supported, false otherwise.
|
||||
@@ -406,6 +405,92 @@ public final class BluetoothHeadset {
|
||||
com.android.internal.R.bool.config_bluetooth_sco_off_call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the outgoing connection.
|
||||
* @hide
|
||||
*/
|
||||
public boolean cancelConnectThread() {
|
||||
if (DBG) log("cancelConnectThread");
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.cancelConnectThread();
|
||||
} catch (RemoteException e) {Log.e(TAG, e.toString());}
|
||||
} else {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the incoming connection.
|
||||
* @hide
|
||||
*/
|
||||
public boolean acceptIncomingConnect(BluetoothDevice device) {
|
||||
if (DBG) log("acceptIncomingConnect");
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.acceptIncomingConnect(device);
|
||||
} catch (RemoteException e) {Log.e(TAG, e.toString());}
|
||||
} else {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the connect thread the incoming connection.
|
||||
* @hide
|
||||
*/
|
||||
public boolean createIncomingConnect(BluetoothDevice device) {
|
||||
if (DBG) log("createIncomingConnect");
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.createIncomingConnect(device);
|
||||
} catch (RemoteException e) {Log.e(TAG, e.toString());}
|
||||
} else {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a Bluetooth Headset.
|
||||
* Note: This is an internal function and shouldn't be exposed
|
||||
* @hide
|
||||
*/
|
||||
public boolean connectHeadsetInternal(BluetoothDevice device) {
|
||||
if (DBG) log("connectHeadsetInternal");
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.connectHeadsetInternal(device);
|
||||
} catch (RemoteException e) {Log.e(TAG, e.toString());}
|
||||
} else {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a Bluetooth Headset.
|
||||
* Note: This is an internal function and shouldn't be exposed
|
||||
* @hide
|
||||
*/
|
||||
public boolean disconnectHeadsetInternal(BluetoothDevice device) {
|
||||
if (DBG) log("disconnectHeadsetInternal");
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.disconnectHeadsetInternal(device);
|
||||
} catch (RemoteException e) {Log.e(TAG, e.toString());}
|
||||
} else {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
if (DBG) Log.d(TAG, "Proxy object connected");
|
||||
|
||||
641
core/java/android/bluetooth/BluetoothProfileConnectionState.java
Normal file
641
core/java/android/bluetooth/BluetoothProfileConnectionState.java
Normal file
@@ -0,0 +1,641 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 android.bluetooth;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Message;
|
||||
import android.server.BluetoothA2dpService;
|
||||
import android.server.BluetoothService;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.HierarchicalState;
|
||||
import com.android.internal.util.HierarchicalStateMachine;
|
||||
|
||||
/**
|
||||
* This class is the Profile connection state machine associated with a remote
|
||||
* device. When the device bonds an instance of this class is created.
|
||||
* This tracks incoming and outgoing connections of all the profiles. Incoming
|
||||
* connections are preferred over outgoing connections and HFP preferred over
|
||||
* A2DP. When the device is unbonded, the instance is removed.
|
||||
*
|
||||
* States:
|
||||
* {@link BondedDevice}: This state represents a bonded device. When in this
|
||||
* state none of the profiles are in transition states.
|
||||
*
|
||||
* {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
|
||||
* state because of a outgoing Connect or Disconnect.
|
||||
*
|
||||
* {@link IncomingHandsfree}: Handsfree profile connection is in a transition
|
||||
* state because of a incoming Connect or Disconnect.
|
||||
*
|
||||
* {@link IncomingA2dp}: A2dp profile connection is in a transition
|
||||
* state because of a incoming Connect or Disconnect.
|
||||
*
|
||||
* {@link OutgoingA2dp}: A2dp profile connection is in a transition
|
||||
* state because of a outgoing Connect or Disconnect.
|
||||
*
|
||||
* Todo(): Write tests for this class, when the Android Mock support is completed.
|
||||
* @hide
|
||||
*/
|
||||
public final class BluetoothProfileConnectionState extends HierarchicalStateMachine {
|
||||
private static final String TAG = "BluetoothProfileConnectionState";
|
||||
private static final boolean DBG = true; //STOPSHIP - Change to false
|
||||
|
||||
public static final int CONNECT_HFP_OUTGOING = 1;
|
||||
public static final int CONNECT_HFP_INCOMING = 2;
|
||||
public static final int CONNECT_A2DP_OUTGOING = 3;
|
||||
public static final int CONNECT_A2DP_INCOMING = 4;
|
||||
|
||||
public static final int DISCONNECT_HFP_OUTGOING = 5;
|
||||
private static final int DISCONNECT_HFP_INCOMING = 6;
|
||||
public static final int DISCONNECT_A2DP_OUTGOING = 7;
|
||||
public static final int DISCONNECT_A2DP_INCOMING = 8;
|
||||
|
||||
public static final int UNPAIR = 9;
|
||||
public static final int AUTO_CONNECT_PROFILES = 10;
|
||||
public static final int TRANSITION_TO_STABLE = 11;
|
||||
|
||||
private static final int AUTO_CONNECT_DELAY = 8000; // 8 secs
|
||||
|
||||
private BondedDevice mBondedDevice = new BondedDevice();
|
||||
private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
|
||||
private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
|
||||
private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
|
||||
private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
|
||||
|
||||
private Context mContext;
|
||||
private BluetoothService mService;
|
||||
private BluetoothA2dpService mA2dpService;
|
||||
private BluetoothHeadset mHeadsetService;
|
||||
private boolean mHeadsetServiceConnected;
|
||||
|
||||
private BluetoothDevice mDevice;
|
||||
private int mHeadsetState;
|
||||
private int mA2dpState;
|
||||
|
||||
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
if (!device.equals(mDevice)) return;
|
||||
|
||||
if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
|
||||
int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
|
||||
int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
|
||||
int initiator = intent.getIntExtra(
|
||||
BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
|
||||
BluetoothHeadset.LOCAL_DISCONNECT);
|
||||
mHeadsetState = newState;
|
||||
if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
|
||||
initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
|
||||
sendMessage(DISCONNECT_HFP_INCOMING);
|
||||
}
|
||||
if (newState == BluetoothHeadset.STATE_CONNECTED ||
|
||||
newState == BluetoothHeadset.STATE_DISCONNECTED) {
|
||||
sendMessage(TRANSITION_TO_STABLE);
|
||||
}
|
||||
} else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
|
||||
int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
|
||||
int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
|
||||
mA2dpState = newState;
|
||||
if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
|
||||
oldState == BluetoothA2dp.STATE_PLAYING) &&
|
||||
newState == BluetoothA2dp.STATE_DISCONNECTED) {
|
||||
sendMessage(DISCONNECT_A2DP_INCOMING);
|
||||
}
|
||||
if (newState == BluetoothA2dp.STATE_CONNECTED ||
|
||||
newState == BluetoothA2dp.STATE_DISCONNECTED) {
|
||||
sendMessage(TRANSITION_TO_STABLE);
|
||||
}
|
||||
} else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
if (!getCurrentState().equals(mBondedDevice)) {
|
||||
Log.e(TAG, "State is: " + getCurrentState());
|
||||
return;
|
||||
}
|
||||
Message msg = new Message();
|
||||
msg.what = AUTO_CONNECT_PROFILES;
|
||||
sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public BluetoothProfileConnectionState(Context context, String address,
|
||||
BluetoothService service, BluetoothA2dpService a2dpService) {
|
||||
super(address);
|
||||
mContext = context;
|
||||
mDevice = new BluetoothDevice(address);
|
||||
mService = service;
|
||||
mA2dpService = a2dpService;
|
||||
|
||||
addState(mBondedDevice);
|
||||
addState(mOutgoingHandsfree);
|
||||
addState(mIncomingHandsfree);
|
||||
addState(mIncomingA2dp);
|
||||
addState(mOutgoingA2dp);
|
||||
setInitialState(mBondedDevice);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
// Fine-grained state broadcasts
|
||||
filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
|
||||
filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
|
||||
mContext.registerReceiver(mBroadcastReceiver, filter);
|
||||
|
||||
HeadsetServiceListener l = new HeadsetServiceListener();
|
||||
}
|
||||
|
||||
private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
|
||||
public HeadsetServiceListener() {
|
||||
mHeadsetService = new BluetoothHeadset(mContext, this);
|
||||
}
|
||||
public void onServiceConnected() {
|
||||
synchronized(BluetoothProfileConnectionState.this) {
|
||||
mHeadsetServiceConnected = true;
|
||||
}
|
||||
}
|
||||
public void onServiceDisconnected() {
|
||||
synchronized(BluetoothProfileConnectionState.this) {
|
||||
mHeadsetServiceConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BondedDevice extends HierarchicalState {
|
||||
@Override
|
||||
protected void enter() {
|
||||
log("Entering ACL Connected state with: " + getCurrentMessage().what);
|
||||
Message m = new Message();
|
||||
m.copyFrom(getCurrentMessage());
|
||||
sendMessageAtFrontOfQueue(m);
|
||||
}
|
||||
@Override
|
||||
protected boolean processMessage(Message message) {
|
||||
log("ACL Connected State -> Processing Message: " + message.what);
|
||||
switch(message.what) {
|
||||
case CONNECT_HFP_OUTGOING:
|
||||
case DISCONNECT_HFP_OUTGOING:
|
||||
transitionTo(mOutgoingHandsfree);
|
||||
break;
|
||||
case CONNECT_HFP_INCOMING:
|
||||
transitionTo(mIncomingHandsfree);
|
||||
break;
|
||||
case DISCONNECT_HFP_INCOMING:
|
||||
transitionTo(mIncomingHandsfree);
|
||||
break;
|
||||
case CONNECT_A2DP_OUTGOING:
|
||||
case DISCONNECT_A2DP_OUTGOING:
|
||||
transitionTo(mOutgoingA2dp);
|
||||
break;
|
||||
case CONNECT_A2DP_INCOMING:
|
||||
case DISCONNECT_A2DP_INCOMING:
|
||||
transitionTo(mIncomingA2dp);
|
||||
break;
|
||||
case UNPAIR:
|
||||
if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
|
||||
sendMessage(DISCONNECT_HFP_OUTGOING);
|
||||
deferMessage(message);
|
||||
break;
|
||||
} else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
|
||||
sendMessage(DISCONNECT_A2DP_OUTGOING);
|
||||
deferMessage(message);
|
||||
break;
|
||||
}
|
||||
processCommand(UNPAIR);
|
||||
break;
|
||||
case AUTO_CONNECT_PROFILES:
|
||||
if (!mHeadsetServiceConnected) {
|
||||
deferMessage(message);
|
||||
} else {
|
||||
if (mHeadsetService.getPriority(mDevice) ==
|
||||
BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
|
||||
mHeadsetService.connectHeadset(mDevice);
|
||||
}
|
||||
if (mA2dpService != null &&
|
||||
mA2dpService.getSinkPriority(mDevice) ==
|
||||
BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
|
||||
mA2dpService.connectSink(mDevice);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TRANSITION_TO_STABLE:
|
||||
// ignore.
|
||||
break;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
return HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutgoingHandsfree extends HierarchicalState {
|
||||
private boolean mStatus = false;
|
||||
private int mCommand;
|
||||
|
||||
@Override
|
||||
protected void enter() {
|
||||
log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
|
||||
mCommand = getCurrentMessage().what;
|
||||
if (mCommand != CONNECT_HFP_OUTGOING &&
|
||||
mCommand != DISCONNECT_HFP_OUTGOING) {
|
||||
Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
|
||||
}
|
||||
mStatus = processCommand(mCommand);
|
||||
if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processMessage(Message message) {
|
||||
log("OutgoingHandsfree State -> Processing Message: " + message.what);
|
||||
Message deferMsg = new Message();
|
||||
int command = message.what;
|
||||
switch(command) {
|
||||
case CONNECT_HFP_OUTGOING:
|
||||
if (command != mCommand) {
|
||||
// Disconnect followed by a connect - defer
|
||||
deferMessage(message);
|
||||
}
|
||||
break;
|
||||
case CONNECT_HFP_INCOMING:
|
||||
if (mCommand == CONNECT_HFP_OUTGOING) {
|
||||
// Cancel outgoing connect, accept incoming
|
||||
cancelCommand(CONNECT_HFP_OUTGOING);
|
||||
transitionTo(mIncomingHandsfree);
|
||||
} else {
|
||||
// We have done the disconnect but we are not
|
||||
// sure which state we are in at this point.
|
||||
deferMessage(message);
|
||||
}
|
||||
break;
|
||||
case CONNECT_A2DP_INCOMING:
|
||||
// accept incoming A2DP, retry HFP_OUTGOING
|
||||
transitionTo(mIncomingA2dp);
|
||||
|
||||
if (mStatus) {
|
||||
deferMsg.what = mCommand;
|
||||
deferMessage(deferMsg);
|
||||
}
|
||||
break;
|
||||
case CONNECT_A2DP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_HFP_OUTGOING:
|
||||
if (mCommand == CONNECT_HFP_OUTGOING) {
|
||||
// Cancel outgoing connect
|
||||
cancelCommand(CONNECT_HFP_OUTGOING);
|
||||
processCommand(DISCONNECT_HFP_OUTGOING);
|
||||
}
|
||||
// else ignore
|
||||
break;
|
||||
case DISCONNECT_HFP_INCOMING:
|
||||
// When this happens the socket would be closed and the headset
|
||||
// state moved to DISCONNECTED, cancel the outgoing thread.
|
||||
// if it still is in CONNECTING state
|
||||
cancelCommand(CONNECT_HFP_OUTGOING);
|
||||
break;
|
||||
case DISCONNECT_A2DP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_A2DP_INCOMING:
|
||||
// Bluez will handle the disconnect. If because of this the outgoing
|
||||
// handsfree connection has failed, then retry.
|
||||
if (mStatus) {
|
||||
deferMsg.what = mCommand;
|
||||
deferMessage(deferMsg);
|
||||
}
|
||||
break;
|
||||
case UNPAIR:
|
||||
case AUTO_CONNECT_PROFILES:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case TRANSITION_TO_STABLE:
|
||||
transitionTo(mBondedDevice);
|
||||
break;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
return HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingHandsfree extends HierarchicalState {
|
||||
private boolean mStatus = false;
|
||||
private int mCommand;
|
||||
|
||||
@Override
|
||||
protected void enter() {
|
||||
log("Entering IncomingHandsfree state with: " + getCurrentMessage().what);
|
||||
mCommand = getCurrentMessage().what;
|
||||
if (mCommand != CONNECT_HFP_INCOMING &&
|
||||
mCommand != DISCONNECT_HFP_INCOMING) {
|
||||
Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
|
||||
}
|
||||
mStatus = processCommand(mCommand);
|
||||
if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processMessage(Message message) {
|
||||
log("IncomingHandsfree State -> Processing Message: " + message.what);
|
||||
switch(message.what) {
|
||||
case CONNECT_HFP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case CONNECT_HFP_INCOMING:
|
||||
// Ignore
|
||||
Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
|
||||
break;
|
||||
case CONNECT_A2DP_INCOMING:
|
||||
// Serialize the commands.
|
||||
deferMessage(message);
|
||||
break;
|
||||
case CONNECT_A2DP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_HFP_OUTGOING:
|
||||
// We don't know at what state we are in the incoming HFP connection state.
|
||||
// We can be changing from DISCONNECTED to CONNECTING, or
|
||||
// from CONNECTING to CONNECTED, so serializing this command is
|
||||
// the safest option.
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_HFP_INCOMING:
|
||||
// Nothing to do here, we will already be DISCONNECTED
|
||||
// by this point.
|
||||
break;
|
||||
case DISCONNECT_A2DP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_A2DP_INCOMING:
|
||||
// Bluez handles incoming A2DP disconnect.
|
||||
// If this causes incoming HFP to fail, it is more of a headset problem
|
||||
// since both connections are incoming ones.
|
||||
break;
|
||||
case UNPAIR:
|
||||
case AUTO_CONNECT_PROFILES:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case TRANSITION_TO_STABLE:
|
||||
transitionTo(mBondedDevice);
|
||||
break;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
return HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutgoingA2dp extends HierarchicalState {
|
||||
private boolean mStatus = false;
|
||||
private int mCommand;
|
||||
|
||||
@Override
|
||||
protected void enter() {
|
||||
log("Entering OutgoingA2dp state with: " + getCurrentMessage().what);
|
||||
mCommand = getCurrentMessage().what;
|
||||
if (mCommand != CONNECT_A2DP_OUTGOING &&
|
||||
mCommand != DISCONNECT_A2DP_OUTGOING) {
|
||||
Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
|
||||
}
|
||||
mStatus = processCommand(mCommand);
|
||||
if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processMessage(Message message) {
|
||||
log("OutgoingA2dp State->Processing Message: " + message.what);
|
||||
Message deferMsg = new Message();
|
||||
switch(message.what) {
|
||||
case CONNECT_HFP_OUTGOING:
|
||||
processCommand(CONNECT_HFP_OUTGOING);
|
||||
|
||||
// Don't cancel A2DP outgoing as there is no guarantee it
|
||||
// will get canceled.
|
||||
// It might already be connected but we might not have got the
|
||||
// A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
|
||||
// The worst case, the connection will fail, retry.
|
||||
// The same applies to Disconnecting an A2DP connection.
|
||||
if (mStatus) {
|
||||
deferMsg.what = mCommand;
|
||||
deferMessage(deferMsg);
|
||||
}
|
||||
break;
|
||||
case CONNECT_HFP_INCOMING:
|
||||
processCommand(CONNECT_HFP_INCOMING);
|
||||
|
||||
// Don't cancel A2DP outgoing as there is no guarantee
|
||||
// it will get canceled.
|
||||
// The worst case, the connection will fail, retry.
|
||||
if (mStatus) {
|
||||
deferMsg.what = mCommand;
|
||||
deferMessage(deferMsg);
|
||||
}
|
||||
break;
|
||||
case CONNECT_A2DP_INCOMING:
|
||||
// Bluez will take care of conflicts between incoming and outgoing
|
||||
// connections.
|
||||
transitionTo(mIncomingA2dp);
|
||||
break;
|
||||
case CONNECT_A2DP_OUTGOING:
|
||||
// Ignore
|
||||
break;
|
||||
case DISCONNECT_HFP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_HFP_INCOMING:
|
||||
// At this point, we are already disconnected
|
||||
// with HFP. Sometimes A2DP connection can
|
||||
// fail due to the disconnection of HFP. So add a retry
|
||||
// for the A2DP.
|
||||
if (mStatus) {
|
||||
deferMsg.what = mCommand;
|
||||
deferMessage(deferMsg);
|
||||
}
|
||||
break;
|
||||
case DISCONNECT_A2DP_OUTGOING:
|
||||
processCommand(DISCONNECT_A2DP_OUTGOING);
|
||||
break;
|
||||
case DISCONNECT_A2DP_INCOMING:
|
||||
// Ignore, will be handled by Bluez
|
||||
break;
|
||||
case UNPAIR:
|
||||
case AUTO_CONNECT_PROFILES:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case TRANSITION_TO_STABLE:
|
||||
transitionTo(mBondedDevice);
|
||||
break;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
return HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingA2dp extends HierarchicalState {
|
||||
private boolean mStatus = false;
|
||||
private int mCommand;
|
||||
|
||||
@Override
|
||||
protected void enter() {
|
||||
log("Entering IncomingA2dp state with: " + getCurrentMessage().what);
|
||||
mCommand = getCurrentMessage().what;
|
||||
if (mCommand != CONNECT_A2DP_INCOMING &&
|
||||
mCommand != DISCONNECT_A2DP_INCOMING) {
|
||||
Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
|
||||
}
|
||||
mStatus = processCommand(mCommand);
|
||||
if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processMessage(Message message) {
|
||||
log("IncomingA2dp State->Processing Message: " + message.what);
|
||||
Message deferMsg = new Message();
|
||||
switch(message.what) {
|
||||
case CONNECT_HFP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case CONNECT_HFP_INCOMING:
|
||||
// Shouldn't happen, but serialize the commands.
|
||||
deferMessage(message);
|
||||
break;
|
||||
case CONNECT_A2DP_INCOMING:
|
||||
// ignore
|
||||
break;
|
||||
case CONNECT_A2DP_OUTGOING:
|
||||
// Defer message and retry
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_HFP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_HFP_INCOMING:
|
||||
// Shouldn't happen but if does, we can handle it.
|
||||
// Depends if the headset can handle it.
|
||||
// Incoming A2DP will be handled by Bluez, Disconnect HFP
|
||||
// the socket would have already been closed.
|
||||
// ignore
|
||||
break;
|
||||
case DISCONNECT_A2DP_OUTGOING:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case DISCONNECT_A2DP_INCOMING:
|
||||
// Ignore, will be handled by Bluez
|
||||
break;
|
||||
case UNPAIR:
|
||||
case AUTO_CONNECT_PROFILES:
|
||||
deferMessage(message);
|
||||
break;
|
||||
case TRANSITION_TO_STABLE:
|
||||
transitionTo(mBondedDevice);
|
||||
break;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
return HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
synchronized void cancelCommand(int command) {
|
||||
if (command == CONNECT_HFP_OUTGOING ) {
|
||||
// Cancel the outgoing thread.
|
||||
if (mHeadsetServiceConnected) {
|
||||
mHeadsetService.cancelConnectThread();
|
||||
}
|
||||
// HeadsetService is down. Phone process most likely crashed.
|
||||
// The thread would have got killed.
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void deferHeadsetMessage(int command) {
|
||||
Message msg = new Message();
|
||||
msg.what = command;
|
||||
deferMessage(msg);
|
||||
}
|
||||
|
||||
synchronized boolean processCommand(int command) {
|
||||
log("Processing command:" + command);
|
||||
switch(command) {
|
||||
case CONNECT_HFP_OUTGOING:
|
||||
if (mHeadsetService != null) {
|
||||
return mHeadsetService.connectHeadsetInternal(mDevice);
|
||||
}
|
||||
break;
|
||||
case CONNECT_HFP_INCOMING:
|
||||
if (!mHeadsetServiceConnected) {
|
||||
deferHeadsetMessage(command);
|
||||
} else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
|
||||
return mHeadsetService.acceptIncomingConnect(mDevice);
|
||||
} else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
|
||||
return mHeadsetService.createIncomingConnect(mDevice);
|
||||
}
|
||||
break;
|
||||
case CONNECT_A2DP_OUTGOING:
|
||||
if (mA2dpService != null) {
|
||||
return mA2dpService.connectSinkInternal(mDevice);
|
||||
}
|
||||
break;
|
||||
case CONNECT_A2DP_INCOMING:
|
||||
// ignore, Bluez takes care
|
||||
return true;
|
||||
case DISCONNECT_HFP_OUTGOING:
|
||||
if (!mHeadsetServiceConnected) {
|
||||
deferHeadsetMessage(command);
|
||||
} else {
|
||||
if (mHeadsetService.getPriority(mDevice) ==
|
||||
BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
|
||||
mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
|
||||
}
|
||||
return mHeadsetService.disconnectHeadsetInternal(mDevice);
|
||||
}
|
||||
break;
|
||||
case DISCONNECT_HFP_INCOMING:
|
||||
// ignore
|
||||
return true;
|
||||
case DISCONNECT_A2DP_INCOMING:
|
||||
// ignore
|
||||
return true;
|
||||
case DISCONNECT_A2DP_OUTGOING:
|
||||
if (mA2dpService != null) {
|
||||
if (mA2dpService.getSinkPriority(mDevice) ==
|
||||
BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
|
||||
mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
|
||||
}
|
||||
return mA2dpService.disconnectSinkInternal(mDevice);
|
||||
}
|
||||
break;
|
||||
case UNPAIR:
|
||||
return mService.removeBondInternal(mDevice.getAddress());
|
||||
default:
|
||||
Log.e(TAG, "Error: Unknown Command");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void log(String message) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "Device:" + mDevice + " Message:" + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,4 +68,8 @@ interface IBluetooth
|
||||
|
||||
int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b);
|
||||
void removeServiceRecord(int handle);
|
||||
|
||||
boolean connectHeadset(String address);
|
||||
boolean disconnectHeadset(String address);
|
||||
boolean notifyIncomingConnection(String address);
|
||||
}
|
||||
|
||||
@@ -33,4 +33,7 @@ interface IBluetoothA2dp {
|
||||
int getSinkState(in BluetoothDevice device);
|
||||
boolean setSinkPriority(in BluetoothDevice device, int priority);
|
||||
int getSinkPriority(in BluetoothDevice device);
|
||||
|
||||
boolean connectSinkInternal(in BluetoothDevice device);
|
||||
boolean disconnectSinkInternal(in BluetoothDevice device);
|
||||
}
|
||||
|
||||
@@ -34,4 +34,10 @@ interface IBluetoothHeadset {
|
||||
boolean setPriority(in BluetoothDevice device, int priority);
|
||||
int getPriority(in BluetoothDevice device);
|
||||
int getBatteryUsageHint();
|
||||
|
||||
boolean createIncomingConnect(in BluetoothDevice device);
|
||||
boolean acceptIncomingConnect(in BluetoothDevice device);
|
||||
boolean cancelConnectThread();
|
||||
boolean connectHeadsetInternal(in BluetoothDevice device);
|
||||
boolean disconnectHeadsetInternal(in BluetoothDevice device);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothUuid;
|
||||
import android.bluetooth.IBluetoothA2dp;
|
||||
import android.os.ParcelUuid;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -35,6 +34,7 @@ import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelUuid;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -55,8 +55,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
|
||||
private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
|
||||
|
||||
private static final int MESSAGE_CONNECT_TO = 1;
|
||||
|
||||
private static final String PROPERTY_STATE = "State";
|
||||
|
||||
private static final String SINK_STATE_DISCONNECTED = "disconnected";
|
||||
@@ -73,6 +71,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
private final BluetoothService mBluetoothService;
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private int mTargetA2dpState;
|
||||
private boolean mAdjustedPriority = false;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
@@ -104,16 +103,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
|
||||
break;
|
||||
}
|
||||
} else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
|
||||
isSinkDevice(device)) {
|
||||
// This device is a preferred sink. Make an A2DP connection
|
||||
// after a delay. We delay to avoid connection collisions,
|
||||
// and to give other profiles such as HFP a chance to
|
||||
// connect first.
|
||||
Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, device);
|
||||
mHandler.sendMessageDelayed(msg, 6000);
|
||||
}
|
||||
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||
synchronized (this) {
|
||||
if (mAudioDevices.containsKey(device)) {
|
||||
@@ -187,6 +176,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
if (mBluetoothService.isEnabled())
|
||||
onBluetoothEnable();
|
||||
mTargetA2dpState = -1;
|
||||
mBluetoothService.setA2dpService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -198,29 +188,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private final Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MESSAGE_CONNECT_TO:
|
||||
BluetoothDevice device = (BluetoothDevice) msg.obj;
|
||||
// check bluetooth is still on, device is still preferred, and
|
||||
// nothing is currently connected
|
||||
if (mBluetoothService.isEnabled() &&
|
||||
getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
|
||||
lookupSinksMatchingStates(new int[] {
|
||||
BluetoothA2dp.STATE_CONNECTING,
|
||||
BluetoothA2dp.STATE_CONNECTED,
|
||||
BluetoothA2dp.STATE_PLAYING,
|
||||
BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) {
|
||||
log("Auto-connecting A2DP to sink " + device);
|
||||
connectSink(device);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private int convertBluezSinkStringtoState(String value) {
|
||||
if (value.equalsIgnoreCase("disconnected"))
|
||||
return BluetoothA2dp.STATE_DISCONNECTED;
|
||||
@@ -308,13 +275,37 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
|
||||
}
|
||||
|
||||
private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
|
||||
if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
|
||||
getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
|
||||
if (path == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean connectSink(BluetoothDevice device) {
|
||||
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||||
"Need BLUETOOTH_ADMIN permission");
|
||||
if (DBG) log("connectSink(" + device + ")");
|
||||
if (!isConnectSinkFeasible(device)) return false;
|
||||
|
||||
return mBluetoothService.connectSink(device.getAddress());
|
||||
}
|
||||
|
||||
public synchronized boolean connectSinkInternal(BluetoothDevice device) {
|
||||
if (!mBluetoothService.isEnabled()) return false;
|
||||
|
||||
int state = mAudioDevices.get(device);
|
||||
|
||||
// ignore if there are any active sinks
|
||||
if (lookupSinksMatchingStates(new int[] {
|
||||
BluetoothA2dp.STATE_CONNECTING,
|
||||
@@ -324,11 +315,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mAudioDevices.get(device) == null && !addAudioSink(device))
|
||||
return false;
|
||||
|
||||
int state = mAudioDevices.get(device);
|
||||
|
||||
switch (state) {
|
||||
case BluetoothA2dp.STATE_CONNECTED:
|
||||
case BluetoothA2dp.STATE_PLAYING:
|
||||
@@ -339,8 +325,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
}
|
||||
|
||||
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
|
||||
if (path == null)
|
||||
return false;
|
||||
|
||||
// State is DISCONNECTED
|
||||
handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
|
||||
@@ -353,11 +337,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean disconnectSink(BluetoothDevice device) {
|
||||
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||||
"Need BLUETOOTH_ADMIN permission");
|
||||
if (DBG) log("disconnectSink(" + device + ")");
|
||||
|
||||
private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
|
||||
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
|
||||
if (path == null) {
|
||||
return false;
|
||||
@@ -370,6 +350,20 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
case BluetoothA2dp.STATE_DISCONNECTING:
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean disconnectSink(BluetoothDevice device) {
|
||||
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||||
"Need BLUETOOTH_ADMIN permission");
|
||||
if (DBG) log("disconnectSink(" + device + ")");
|
||||
if (!isDisconnectSinkFeasible(device)) return false;
|
||||
return mBluetoothService.disconnectSink(device.getAddress());
|
||||
}
|
||||
|
||||
public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
|
||||
int state = getSinkState(device);
|
||||
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
|
||||
|
||||
// State is CONNECTING or CONNECTED or PLAYING
|
||||
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
|
||||
@@ -504,6 +498,12 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
|
||||
}
|
||||
|
||||
if (state == BluetoothA2dp.STATE_CONNECTED) {
|
||||
// We will only have 1 device with AUTO_CONNECT priority
|
||||
// To be backward compatible set everyone else to have PRIORITY_ON
|
||||
adjustOtherSinkPriorities(device);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
|
||||
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
|
||||
intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
|
||||
@@ -514,6 +514,18 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
|
||||
if (!mAdjustedPriority) {
|
||||
for (BluetoothDevice device : mAdapter.getBondedDevices()) {
|
||||
if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
|
||||
!device.equals(connectedDevice)) {
|
||||
setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
|
||||
}
|
||||
}
|
||||
mAdjustedPriority = true;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
|
||||
Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
|
||||
if (mAudioDevices.isEmpty()) {
|
||||
|
||||
@@ -566,6 +566,7 @@ class BluetoothEventLoop {
|
||||
authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
|
||||
if (authorized) {
|
||||
Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
|
||||
mBluetoothService.notifyIncomingA2dpConnection(address);
|
||||
} else {
|
||||
Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.bluetooth.BluetoothProfileConnectionState;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.bluetooth.BluetoothUuid;
|
||||
import android.bluetooth.IBluetooth;
|
||||
@@ -112,7 +113,7 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
BluetoothUuid.HSP,
|
||||
BluetoothUuid.ObexObjectPush };
|
||||
|
||||
|
||||
// TODO(): Optimize all these string handling
|
||||
private final Map<String, String> mAdapterProperties;
|
||||
private final HashMap<String, Map<String, String>> mDeviceProperties;
|
||||
|
||||
@@ -122,6 +123,9 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
|
||||
private final HashMap<Integer, Integer> mServiceRecordToPid;
|
||||
|
||||
private final HashMap<String, BluetoothProfileConnectionState> mProfileConnectionMgr;
|
||||
|
||||
private BluetoothA2dpService mA2dpService;
|
||||
private static String mDockAddress;
|
||||
private String mDockPin;
|
||||
|
||||
@@ -179,6 +183,7 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
mUuidIntentTracker = new ArrayList<String>();
|
||||
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
|
||||
mServiceRecordToPid = new HashMap<Integer, Integer>();
|
||||
mProfileConnectionMgr = new HashMap<String, BluetoothProfileConnectionState>();
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
registerForAirplaneMode(filter);
|
||||
@@ -187,7 +192,7 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
mContext.registerReceiver(mReceiver, filter);
|
||||
}
|
||||
|
||||
public static synchronized String readDockBluetoothAddress() {
|
||||
public static synchronized String readDockBluetoothAddress() {
|
||||
if (mDockAddress != null) return mDockAddress;
|
||||
|
||||
BufferedInputStream file = null;
|
||||
@@ -534,6 +539,7 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
mIsDiscovering = false;
|
||||
mBondState.readAutoPairingData();
|
||||
mBondState.loadBondState();
|
||||
initProfileState();
|
||||
mHandler.sendMessageDelayed(
|
||||
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
|
||||
|
||||
@@ -648,6 +654,12 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
if (state == BluetoothDevice.BOND_BONDED) {
|
||||
addProfileState(address);
|
||||
} else if (state == BluetoothDevice.BOND_NONE) {
|
||||
removeProfileState(address);
|
||||
}
|
||||
|
||||
if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
|
||||
reason + ")");
|
||||
Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
@@ -1167,6 +1179,16 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||||
return false;
|
||||
}
|
||||
BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
state.sendMessage(BluetoothProfileConnectionState.UNPAIR);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean removeBondInternal(String address) {
|
||||
return removeDeviceNative(getObjectPathFromAddress(address));
|
||||
}
|
||||
|
||||
@@ -1919,6 +1941,104 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
|
||||
}
|
||||
|
||||
public boolean connectHeadset(String address) {
|
||||
BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
state.sendMessage(BluetoothProfileConnectionState.CONNECT_HFP_OUTGOING);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean disconnectHeadset(String address) {
|
||||
BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
state.sendMessage(BluetoothProfileConnectionState.DISCONNECT_HFP_OUTGOING);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean connectSink(String address) {
|
||||
BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
state.sendMessage(BluetoothProfileConnectionState.CONNECT_A2DP_OUTGOING);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean disconnectSink(String address) {
|
||||
BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
state.sendMessage(BluetoothProfileConnectionState.DISCONNECT_A2DP_OUTGOING);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private BluetoothProfileConnectionState addProfileState(String address) {
|
||||
BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
|
||||
if (state != null) return state;
|
||||
|
||||
state = new BluetoothProfileConnectionState(mContext, address, this, mA2dpService);
|
||||
mProfileConnectionMgr.put(address, state);
|
||||
state.start();
|
||||
return state;
|
||||
}
|
||||
|
||||
private void removeProfileState(String address) {
|
||||
mProfileConnectionMgr.remove(address);
|
||||
}
|
||||
|
||||
private void initProfileState() {
|
||||
String []bonds = null;
|
||||
String val = getPropertyInternal("Devices");
|
||||
if (val != null) {
|
||||
bonds = val.split(",");
|
||||
}
|
||||
if (bonds == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String path : bonds) {
|
||||
String address = getAddressFromObjectPath(path);
|
||||
BluetoothProfileConnectionState state = addProfileState(address);
|
||||
// Allow 8 secs for SDP records to get registered.
|
||||
Message msg = new Message();
|
||||
msg.what = BluetoothProfileConnectionState.AUTO_CONNECT_PROFILES;
|
||||
state.sendMessageDelayed(msg, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean notifyIncomingConnection(String address) {
|
||||
BluetoothProfileConnectionState state =
|
||||
mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
Message msg = new Message();
|
||||
msg.what = BluetoothProfileConnectionState.CONNECT_HFP_INCOMING;
|
||||
state.sendMessage(msg);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*package*/ boolean notifyIncomingA2dpConnection(String address) {
|
||||
BluetoothProfileConnectionState state =
|
||||
mProfileConnectionMgr.get(address);
|
||||
if (state != null) {
|
||||
Message msg = new Message();
|
||||
msg.what = BluetoothProfileConnectionState.CONNECT_A2DP_INCOMING;
|
||||
state.sendMessage(msg);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*package*/ void setA2dpService(BluetoothA2dpService a2dpService) {
|
||||
mA2dpService = a2dpService;
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user