diff options
| -rw-r--r-- | core/java/android/bluetooth/BluetoothDeviceProfileState.java | 665 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothHeadset.java | 95 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothProfileState.java | 144 | ||||
| -rw-r--r-- | core/java/android/bluetooth/IBluetooth.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/bluetooth/IBluetoothA2dp.aidl | 3 | ||||
| -rw-r--r-- | core/java/android/bluetooth/IBluetoothHeadset.aidl | 10 | ||||
| -rw-r--r-- | core/java/android/server/BluetoothA2dpService.java | 108 | ||||
| -rw-r--r-- | core/java/android/server/BluetoothEventLoop.java | 1 | ||||
| -rw-r--r-- | core/java/android/server/BluetoothService.java | 146 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 18 | ||||
| -rw-r--r-- | services/java/com/android/server/PackageManagerService.java | 50 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiStateTracker.java | 2 |
12 files changed, 1158 insertions, 88 deletions
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java new file mode 100644 index 000000000000..8e655e213d62 --- /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 95e61b6f6ace..4a91a8c90721 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -189,11 +189,11 @@ public final class BluetoothHeadset { * @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 @@ public final class BluetoothHeadset { * 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 @@ 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"); diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java new file mode 100644 index 000000000000..946dcaa01c86 --- /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 08687795d932..ea71034f5b4a 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -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); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 168fe3b252da..40f10583b637 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -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); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 6cccd506e269..d96f0ca0de8d 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -24,14 +24,20 @@ import android.bluetooth.BluetoothDevice; * {@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 ac89934ada60..a52a221e01aa 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -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()) { diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index c0e4600ae24c..e1d3f13cacce 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -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); } diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index c0affd3755d6..31e5a7b6b495 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -28,6 +28,8 @@ import android.bluetooth.BluetoothAdapter; 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 @@ 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 +124,11 @@ public class BluetoothService extends IBluetooth.Stub { 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 @@ public class BluetoothService extends IBluetooth.Stub { 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 @@ 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 +547,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 +662,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 +1187,16 @@ public class BluetoothService extends IBluetooth.Stub { 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 @@ public class BluetoothService extends IBluetooth.Stub { // 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 @@ public class BluetoothService extends IBluetooth.Stub { 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 5c278d908ff8..bd78f9363f88 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -1092,16 +1092,20 @@ public class AudioService extends IAudioService.Stub { 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 599023ce9430..79da3cf95d1c 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -3648,21 +3648,19 @@ class PackageManagerService extends IPackageManager.Stub { 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 @@ class PackageManagerService extends IPackageManager.Stub { 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 @@ class PackageManagerService extends IPackageManager.Stub { // 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 978d821ec0db..2c8be31a22a6 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -2324,7 +2324,7 @@ public class WifiStateTracker extends NetworkStateTracker { * @return Whether to disable coexistence mode. */ private boolean shouldDisableCoexistenceMode() { - int state = mBluetoothHeadset.getState(); + int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset()); return state == BluetoothHeadset.STATE_DISCONNECTED; } } |