Merge "Initial checkin of preliminary support for "http live" streaming in stagefright." into kraken
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
new file mode 100644
index 0000000..8e655e2
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2010 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 BluetoothDeviceProfileState extends HierarchicalStateMachine {
+ private static final String TAG = "BluetoothDeviceProfileState";
+ 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 = 6000; // 6 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);
+ }
+ }
+ };
+
+ private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
+ // This works only because these broadcast intents are "sticky"
+ Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ if (i != null) {
+ int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device != null && autoConnectDevice.equals(device)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public BluetoothDeviceProfileState(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(BluetoothDeviceProfileState.this) {
+ mHeadsetServiceConnected = true;
+ }
+ }
+ public void onServiceDisconnected() {
+ synchronized(BluetoothDeviceProfileState.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 (isPhoneDocked(mDevice)) {
+ // Don't auto connect to docks.
+ break;
+ } else if (!mHeadsetServiceConnected) {
+ deferMessage(message);
+ } else {
+ if (mHeadsetService.getPriority(mDevice) ==
+ BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
+ !mHeadsetService.isConnected(mDevice)) {
+ mHeadsetService.connectHeadset(mDevice);
+ }
+ if (mA2dpService != null &&
+ mA2dpService.getSinkPriority(mDevice) ==
+ BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
+ mA2dpService.getConnectedSinks().length == 0) {
+ 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;
+ }
+
+ /*package*/ BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ private void log(String message) {
+ if (DBG) {
+ Log.i(TAG, "Device:" + mDevice + " Message:" + message);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 95e61b6..4a91a8c 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -189,11 +189,11 @@
* @return One of the STATE_ return codes, or STATE_ERROR if this proxy
* object is currently not connected to the Headset service.
*/
- public int getState() {
+ public int getState(BluetoothDevice device) {
if (DBG) log("getState()");
if (mService != null) {
try {
- return mService.getState();
+ return mService.getState(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
Log.w(TAG, "Proxy not attached to service");
@@ -271,11 +271,11 @@
* be made asynchornous. Returns false if this proxy object is
* not currently connected to the Headset service.
*/
- public boolean disconnectHeadset() {
+ public boolean disconnectHeadset(BluetoothDevice device) {
if (DBG) log("disconnectHeadset()");
if (mService != null) {
try {
- mService.disconnectHeadset();
+ mService.disconnectHeadset(device);
return true;
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
@@ -395,7 +395,6 @@
}
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 @@
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");
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
new file mode 100644
index 0000000..946dcaa
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothProfileState.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 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.util.Log;
+
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+/**
+ * This state machine is used to serialize the connections
+ * to a particular profile. Currently, we only allow one device
+ * to be connected to a particular profile.
+ * States:
+ * {@link StableState} : No pending commands. Send the
+ * command to the appropriate remote device specific state machine.
+ *
+ * {@link PendingCommandState} : A profile connection / disconnection
+ * command is being executed. This will result in a profile state
+ * change. Defer all commands.
+ * @hide
+ */
+
+public class BluetoothProfileState extends HierarchicalStateMachine {
+ private static final boolean DBG = true; // STOPSHIP - change to false.
+ private static final String TAG = "BluetoothProfileState";
+
+ public static int HFP = 0;
+ public static int A2DP = 1;
+
+ private static int TRANSITION_TO_STABLE = 100;
+
+ private int mProfile;
+ private BluetoothDevice mPendingDevice;
+ private PendingCommandState mPendingCommandState = new PendingCommandState();
+ private StableState mStableState = new StableState();
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
+ int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
+ if (mProfile == HFP && (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);
+ if (mProfile == A2DP && (newState == BluetoothA2dp.STATE_CONNECTED ||
+ newState == BluetoothA2dp.STATE_DISCONNECTED)) {
+ sendMessage(TRANSITION_TO_STABLE);
+ }
+ }
+ }
+ };
+
+ public BluetoothProfileState(Context context, int profile) {
+ super("BluetoothProfileState:" + profile);
+ mProfile = profile;
+ addState(mStableState);
+ addState(mPendingCommandState);
+ setInitialState(mStableState);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private class StableState extends HierarchicalState {
+ @Override
+ protected void enter() {
+ log("Entering Stable State");
+ mPendingDevice = null;
+ }
+
+ @Override
+ protected boolean processMessage(Message msg) {
+ if (msg.what != TRANSITION_TO_STABLE) {
+ transitionTo(mPendingCommandState);
+ }
+ return true;
+ }
+ }
+
+ private class PendingCommandState extends HierarchicalState {
+ @Override
+ protected void enter() {
+ log("Entering PendingCommandState State");
+ dispatchMessage(getCurrentMessage());
+ }
+
+ @Override
+ protected boolean processMessage(Message msg) {
+ if (msg.what == TRANSITION_TO_STABLE) {
+ transitionTo(mStableState);
+ } else {
+ dispatchMessage(msg);
+ }
+ return true;
+ }
+
+ private void dispatchMessage(Message msg) {
+ BluetoothDeviceProfileState deviceProfileMgr =
+ (BluetoothDeviceProfileState)msg.obj;
+ int cmd = msg.arg1;
+ if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) {
+ mPendingDevice = deviceProfileMgr.getDevice();
+ deviceProfileMgr.sendMessage(cmd);
+ } else {
+ Message deferMsg = new Message();
+ deferMsg.arg1 = cmd;
+ deferMsg.obj = deviceProfileMgr;
+ deferMessage(deferMsg);
+ }
+ }
+ }
+
+ private void log(String message) {
+ if (DBG) {
+ Log.i(TAG, "Message:" + message);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 0868779..ea71034 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -68,4 +68,8 @@
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);
}
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 168fe3b..40f1058 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -33,4 +33,7 @@
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);
}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 6cccd50..d96f0ca 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -24,14 +24,20 @@
* {@hide}
*/
interface IBluetoothHeadset {
- int getState();
+ int getState(in BluetoothDevice device);
BluetoothDevice getCurrentHeadset();
boolean connectHeadset(in BluetoothDevice device);
- void disconnectHeadset();
+ void disconnectHeadset(in BluetoothDevice device);
boolean isConnected(in BluetoothDevice device);
boolean startVoiceRecognition();
boolean stopVoiceRecognition();
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);
}
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index ac89934..a52a221 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -27,7 +27,6 @@
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.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 @@
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 @@
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 @@
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 @@
if (mBluetoothService.isEnabled())
onBluetoothEnable();
mTargetA2dpState = -1;
+ mBluetoothService.setA2dpService(this);
}
@Override
@@ -198,29 +188,6 @@
}
}
- 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 @@
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 @@
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 @@
}
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (path == null)
- return false;
// State is DISCONNECTED
handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
@@ -353,11 +337,7 @@
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 @@
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 @@
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 @@
}
}
+ 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()) {
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index c0e4600..e1d3f13 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -566,6 +566,7 @@
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);
}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index c0affd3..31e5a7b 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -28,6 +28,8 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothProfileState;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth;
@@ -112,7 +114,7 @@
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 +124,11 @@
private final HashMap<Integer, Integer> mServiceRecordToPid;
+ private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
+ private final BluetoothProfileState mA2dpProfileState;
+ private final BluetoothProfileState mHfpProfileState;
+
+ private BluetoothA2dpService mA2dpService;
private static String mDockAddress;
private String mDockPin;
@@ -179,6 +186,12 @@
mUuidIntentTracker = new ArrayList<String>();
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
mServiceRecordToPid = new HashMap<Integer, Integer>();
+ mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
+ mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
+ mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
+
+ mHfpProfileState.start();
+ mA2dpProfileState.start();
IntentFilter filter = new IntentFilter();
registerForAirplaneMode(filter);
@@ -187,7 +200,7 @@
mContext.registerReceiver(mReceiver, filter);
}
- public static synchronized String readDockBluetoothAddress() {
+ public static synchronized String readDockBluetoothAddress() {
if (mDockAddress != null) return mDockAddress;
BufferedInputStream file = null;
@@ -534,6 +547,7 @@
mIsDiscovering = false;
mBondState.readAutoPairingData();
mBondState.loadBondState();
+ initProfileState();
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
@@ -648,6 +662,12 @@
}
}
+ 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 +1187,16 @@
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
return false;
}
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ state.sendMessage(BluetoothDeviceProfileState.UNPAIR);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public synchronized boolean removeBondInternal(String address) {
return removeDeviceNative(getObjectPathFromAddress(address));
}
@@ -1836,7 +1866,7 @@
// Rather not do this from here, but no-where else and I need this
// dump
pw.println("\n--Headset Service--");
- switch (headset.getState()) {
+ switch (headset.getState(headset.getCurrentHeadset())) {
case BluetoothHeadset.STATE_DISCONNECTED:
pw.println("getState() = STATE_DISCONNECTED");
break;
@@ -1919,6 +1949,116 @@
if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
}
+ public boolean connectHeadset(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING;
+ msg.obj = state;
+ mHfpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean disconnectHeadset(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING;
+ msg.obj = state;
+ mHfpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean connectSink(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
+ msg.obj = state;
+ mA2dpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean disconnectSink(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING;
+ msg.obj = state;
+ mA2dpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ private BluetoothDeviceProfileState addProfileState(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) return state;
+
+ state = new BluetoothDeviceProfileState(mContext, address, this, mA2dpService);
+ mDeviceProfileState.put(address, state);
+ state.start();
+ return state;
+ }
+
+ private void removeProfileState(String address) {
+ mDeviceProfileState.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);
+ BluetoothDeviceProfileState state = addProfileState(address);
+ // Allow 8 secs for SDP records to get registered.
+ Message msg = new Message();
+ msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;
+ state.sendMessageDelayed(msg, 8000);
+ }
+ }
+
+ public boolean notifyIncomingConnection(String address) {
+ BluetoothDeviceProfileState state =
+ mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING;
+ state.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ /*package*/ boolean notifyIncomingA2dpConnection(String address) {
+ BluetoothDeviceProfileState state =
+ mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.what = BluetoothDeviceProfileState.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);
}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 5c278d9..bd78f93 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1092,16 +1092,20 @@
private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener =
new BluetoothHeadset.ServiceListener() {
public void onServiceConnected() {
- if (mBluetoothHeadset != null &&
- mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) {
- mBluetoothHeadsetConnected = true;
+ if (mBluetoothHeadset != null) {
+ BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset();
+ if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_CONNECTED) {
+ mBluetoothHeadsetConnected = true;
+ }
}
}
public void onServiceDisconnected() {
- if (mBluetoothHeadset != null &&
- mBluetoothHeadset.getState() == BluetoothHeadset.STATE_DISCONNECTED) {
- mBluetoothHeadsetConnected = false;
- clearAllScoClients();
+ if (mBluetoothHeadset != null) {
+ BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset();
+ if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_DISCONNECTED) {
+ mBluetoothHeadsetConnected = false;
+ clearAllScoClients();
+ }
}
}
};
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 599023c..79da3cf 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -3648,21 +3648,19 @@
installedNativeLibraries = true;
+ // Always extract the shared library
String sharedLibraryFilePath = sharedLibraryDir.getPath() +
File.separator + libFileName;
File sharedLibraryFile = new File(sharedLibraryFilePath);
- if (! sharedLibraryFile.exists() ||
- sharedLibraryFile.length() != entry.getSize() ||
- sharedLibraryFile.lastModified() != entry.getTime()) {
- if (Config.LOGD) {
- Log.d(TAG, "Caching shared lib " + entry.getName());
- }
- if (mInstaller == null) {
- sharedLibraryDir.mkdir();
- }
- cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir,
- sharedLibraryFile);
+
+ if (Config.LOGD) {
+ Log.d(TAG, "Caching shared lib " + entry.getName());
}
+ if (mInstaller == null) {
+ sharedLibraryDir.mkdir();
+ }
+ cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir,
+ sharedLibraryFile);
}
if (!hasNativeLibraries)
return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
@@ -3704,18 +3702,16 @@
String installGdbServerPath = installGdbServerDir.getPath() +
"/" + GDBSERVER;
File installGdbServerFile = new File(installGdbServerPath);
- if (! installGdbServerFile.exists() ||
- installGdbServerFile.length() != entry.getSize() ||
- installGdbServerFile.lastModified() != entry.getTime()) {
- if (Config.LOGD) {
- Log.d(TAG, "Caching gdbserver " + entry.getName());
- }
- if (mInstaller == null) {
- installGdbServerDir.mkdir();
- }
- cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir,
- installGdbServerFile);
+
+ if (Config.LOGD) {
+ Log.d(TAG, "Caching gdbserver " + entry.getName());
}
+ if (mInstaller == null) {
+ installGdbServerDir.mkdir();
+ }
+ cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir,
+ installGdbServerFile);
+
return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
}
return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
@@ -3729,6 +3725,16 @@
// one if ro.product.cpu.abi2 is defined.
//
private int cachePackageSharedLibsLI(PackageParser.Package pkg, File scanFile) {
+ // Remove all native binaries from a directory. This is used when upgrading
+ // a package: in case the new .apk doesn't contain a native binary that was
+ // in the old one (and thus installed), we need to remove it from
+ // /data/data/<appname>/lib
+ //
+ // The simplest way to do that is to remove all files in this directory,
+ // since it is owned by "system", applications are not supposed to write
+ // anything there.
+ removeNativeBinariesLI(pkg);
+
String cpuAbi = Build.CPU_ABI;
try {
int result = cachePackageSharedLibsForAbiLI(pkg, scanFile, cpuAbi);
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 978d821..2c8be31 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -2324,7 +2324,7 @@
* @return Whether to disable coexistence mode.
*/
private boolean shouldDisableCoexistenceMode() {
- int state = mBluetoothHeadset.getState();
+ int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset());
return state == BluetoothHeadset.STATE_DISCONNECTED;
}
}