From 9b637e5985f9a86f39d70335c0390ade3716592a Mon Sep 17 00:00:00 2001
From: Jaikumar Ganesh <jaikumar@google.com>
Date: Wed, 2 Jun 2010 14:36:14 -0700
Subject: Add a new state machine for handling the incoming / outgoing profile
 connections.

Change-Id: I5fc9170b5e24c4a52a6f2ef4ca7a8bac65271941
---
 core/java/android/bluetooth/BluetoothHeadset.java  |  87 ++-
 .../bluetooth/BluetoothProfileConnectionState.java | 641 +++++++++++++++++++++
 core/java/android/bluetooth/IBluetooth.aidl        |   4 +
 core/java/android/bluetooth/IBluetoothA2dp.aidl    |   3 +
 core/java/android/bluetooth/IBluetoothHeadset.aidl |   6 +
 core/java/android/server/BluetoothA2dpService.java | 108 ++--
 core/java/android/server/BluetoothEventLoop.java   |   1 +
 core/java/android/server/BluetoothService.java     | 124 +++-
 8 files changed, 923 insertions(+), 51 deletions(-)
 create mode 100644 core/java/android/bluetooth/BluetoothProfileConnectionState.java

(limited to 'core')

diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 95e61b6..9d633fe 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -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/BluetoothProfileConnectionState.java b/core/java/android/bluetooth/BluetoothProfileConnectionState.java
new file mode 100644
index 0000000..a58b858
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothProfileConnectionState.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Message;
+import android.server.BluetoothA2dpService;
+import android.server.BluetoothService;
+import android.util.Log;
+
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+/**
+ * This class is the Profile connection state machine associated with a remote
+ * device. When the device bonds an instance of this class is created.
+ * This tracks incoming and outgoing connections of all the profiles. Incoming
+ * connections are preferred over outgoing connections and HFP preferred over
+ * A2DP. When the device is unbonded, the instance is removed.
+ *
+ * States:
+ * {@link BondedDevice}: This state represents a bonded device. When in this
+ * state none of the profiles are in transition states.
+ *
+ * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
+ * state because of a outgoing Connect or Disconnect.
+ *
+ * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
+ * state because of a incoming Connect or Disconnect.
+ *
+ * {@link IncomingA2dp}: A2dp profile connection is in a transition
+ * state because of a incoming Connect or Disconnect.
+ *
+ * {@link OutgoingA2dp}: A2dp profile connection is in a transition
+ * state because of a outgoing Connect or Disconnect.
+ *
+ * Todo(): Write tests for this class, when the Android Mock support is completed.
+ * @hide
+ */
+public final class BluetoothProfileConnectionState extends HierarchicalStateMachine {
+    private static final String TAG = "BluetoothProfileConnectionState";
+    private static final boolean DBG = true; //STOPSHIP - Change to false
+
+    public static final int CONNECT_HFP_OUTGOING = 1;
+    public static final int CONNECT_HFP_INCOMING = 2;
+    public static final int CONNECT_A2DP_OUTGOING = 3;
+    public static final int CONNECT_A2DP_INCOMING = 4;
+
+    public static final int DISCONNECT_HFP_OUTGOING = 5;
+    private static final int DISCONNECT_HFP_INCOMING = 6;
+    public static final int DISCONNECT_A2DP_OUTGOING = 7;
+    public static final int DISCONNECT_A2DP_INCOMING = 8;
+
+    public static final int UNPAIR = 9;
+    public static final int AUTO_CONNECT_PROFILES = 10;
+    public static final int TRANSITION_TO_STABLE = 11;
+
+    private static final int AUTO_CONNECT_DELAY = 8000; // 8 secs
+
+    private BondedDevice mBondedDevice = new BondedDevice();
+    private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
+    private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
+    private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
+    private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
+
+    private Context mContext;
+    private BluetoothService mService;
+    private BluetoothA2dpService mA2dpService;
+    private BluetoothHeadset  mHeadsetService;
+    private boolean mHeadsetServiceConnected;
+
+    private BluetoothDevice mDevice;
+    private int mHeadsetState;
+    private int mA2dpState;
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (!device.equals(mDevice)) return;
+
+            if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
+                int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
+                int initiator = intent.getIntExtra(
+                    BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
+                    BluetoothHeadset.LOCAL_DISCONNECT);
+                mHeadsetState = newState;
+                if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
+                    initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
+                    sendMessage(DISCONNECT_HFP_INCOMING);
+                }
+                if (newState == BluetoothHeadset.STATE_CONNECTED ||
+                    newState == BluetoothHeadset.STATE_DISCONNECTED) {
+                    sendMessage(TRANSITION_TO_STABLE);
+                }
+            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
+                int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
+                mA2dpState = newState;
+                if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
+                           oldState == BluetoothA2dp.STATE_PLAYING) &&
+                           newState == BluetoothA2dp.STATE_DISCONNECTED) {
+                    sendMessage(DISCONNECT_A2DP_INCOMING);
+                }
+                if (newState == BluetoothA2dp.STATE_CONNECTED ||
+                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
+                    sendMessage(TRANSITION_TO_STABLE);
+                }
+            } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
+                if (!getCurrentState().equals(mBondedDevice)) {
+                    Log.e(TAG, "State is: " + getCurrentState());
+                    return;
+                }
+                Message msg = new Message();
+                msg.what = AUTO_CONNECT_PROFILES;
+                sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
+            }
+      }
+    };
+
+    public BluetoothProfileConnectionState(Context context, String address,
+          BluetoothService service, BluetoothA2dpService a2dpService) {
+        super(address);
+        mContext = context;
+        mDevice = new BluetoothDevice(address);
+        mService = service;
+        mA2dpService = a2dpService;
+
+        addState(mBondedDevice);
+        addState(mOutgoingHandsfree);
+        addState(mIncomingHandsfree);
+        addState(mIncomingA2dp);
+        addState(mOutgoingA2dp);
+        setInitialState(mBondedDevice);
+
+        IntentFilter filter = new IntentFilter();
+        // Fine-grained state broadcasts
+        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
+        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        HeadsetServiceListener l = new HeadsetServiceListener();
+    }
+
+    private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
+        public HeadsetServiceListener() {
+            mHeadsetService = new BluetoothHeadset(mContext, this);
+        }
+        public void onServiceConnected() {
+            synchronized(BluetoothProfileConnectionState.this) {
+                mHeadsetServiceConnected = true;
+            }
+        }
+        public void onServiceDisconnected() {
+            synchronized(BluetoothProfileConnectionState.this) {
+                mHeadsetServiceConnected = false;
+            }
+        }
+    }
+
+    private class BondedDevice extends HierarchicalState {
+        @Override
+        protected void enter() {
+            log("Entering ACL Connected state with: " + getCurrentMessage().what);
+            Message m = new Message();
+            m.copyFrom(getCurrentMessage());
+            sendMessageAtFrontOfQueue(m);
+        }
+        @Override
+        protected boolean processMessage(Message message) {
+            log("ACL Connected State -> Processing Message: " + message.what);
+            switch(message.what) {
+                case CONNECT_HFP_OUTGOING:
+                case DISCONNECT_HFP_OUTGOING:
+                    transitionTo(mOutgoingHandsfree);
+                    break;
+                case CONNECT_HFP_INCOMING:
+                    transitionTo(mIncomingHandsfree);
+                    break;
+                case DISCONNECT_HFP_INCOMING:
+                    transitionTo(mIncomingHandsfree);
+                    break;
+                case CONNECT_A2DP_OUTGOING:
+                case DISCONNECT_A2DP_OUTGOING:
+                    transitionTo(mOutgoingA2dp);
+                    break;
+                case CONNECT_A2DP_INCOMING:
+                case DISCONNECT_A2DP_INCOMING:
+                    transitionTo(mIncomingA2dp);
+                    break;
+                case UNPAIR:
+                    if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
+                        sendMessage(DISCONNECT_HFP_OUTGOING);
+                        deferMessage(message);
+                        break;
+                    } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
+                        sendMessage(DISCONNECT_A2DP_OUTGOING);
+                        deferMessage(message);
+                        break;
+                    }
+                    processCommand(UNPAIR);
+                    break;
+                case AUTO_CONNECT_PROFILES:
+                    if (!mHeadsetServiceConnected) {
+                        deferMessage(message);
+                    } else {
+                        if (mHeadsetService.getPriority(mDevice) ==
+                            BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
+                            mHeadsetService.connectHeadset(mDevice);
+                        }
+                        if (mA2dpService != null &&
+                            mA2dpService.getSinkPriority(mDevice) ==
+                              BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
+                            mA2dpService.connectSink(mDevice);
+                        }
+                    }
+                    break;
+                case TRANSITION_TO_STABLE:
+                    // ignore.
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    private class OutgoingHandsfree extends HierarchicalState {
+        private boolean mStatus = false;
+        private int mCommand;
+
+        @Override
+        protected void enter() {
+            log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
+            mCommand = getCurrentMessage().what;
+            if (mCommand != CONNECT_HFP_OUTGOING &&
+                mCommand != DISCONNECT_HFP_OUTGOING) {
+                Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
+            }
+            mStatus = processCommand(mCommand);
+            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+        }
+
+        @Override
+        protected boolean processMessage(Message message) {
+            log("OutgoingHandsfree State -> Processing Message: " + message.what);
+            Message deferMsg = new Message();
+            int command = message.what;
+            switch(command) {
+                case CONNECT_HFP_OUTGOING:
+                    if (command != mCommand) {
+                        // Disconnect followed by a connect - defer
+                        deferMessage(message);
+                    }
+                    break;
+                case CONNECT_HFP_INCOMING:
+                    if (mCommand == CONNECT_HFP_OUTGOING) {
+                        // Cancel outgoing connect, accept incoming
+                        cancelCommand(CONNECT_HFP_OUTGOING);
+                        transitionTo(mIncomingHandsfree);
+                    } else {
+                        // We have done the disconnect but we are not
+                        // sure which state we are in at this point.
+                        deferMessage(message);
+                    }
+                    break;
+                case CONNECT_A2DP_INCOMING:
+                    // accept incoming A2DP, retry HFP_OUTGOING
+                    transitionTo(mIncomingA2dp);
+
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case CONNECT_A2DP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_HFP_OUTGOING:
+                    if (mCommand == CONNECT_HFP_OUTGOING) {
+                        // Cancel outgoing connect
+                        cancelCommand(CONNECT_HFP_OUTGOING);
+                        processCommand(DISCONNECT_HFP_OUTGOING);
+                    }
+                    // else ignore
+                    break;
+                case DISCONNECT_HFP_INCOMING:
+                    // When this happens the socket would be closed and the headset
+                    // state moved to DISCONNECTED, cancel the outgoing thread.
+                    // if it still is in CONNECTING state
+                    cancelCommand(CONNECT_HFP_OUTGOING);
+                    break;
+                case DISCONNECT_A2DP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_A2DP_INCOMING:
+                    // Bluez will handle the disconnect. If because of this the outgoing
+                    // handsfree connection has failed, then retry.
+                    if (mStatus) {
+                       deferMsg.what = mCommand;
+                       deferMessage(deferMsg);
+                    }
+                    break;
+                case UNPAIR:
+                case AUTO_CONNECT_PROFILES:
+                    deferMessage(message);
+                    break;
+                case TRANSITION_TO_STABLE:
+                    transitionTo(mBondedDevice);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    private class IncomingHandsfree extends HierarchicalState {
+        private boolean mStatus = false;
+        private int mCommand;
+
+        @Override
+        protected void enter() {
+            log("Entering IncomingHandsfree state with: " + getCurrentMessage().what);
+            mCommand = getCurrentMessage().what;
+            if (mCommand != CONNECT_HFP_INCOMING &&
+                mCommand != DISCONNECT_HFP_INCOMING) {
+                Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
+            }
+            mStatus = processCommand(mCommand);
+            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+        }
+
+        @Override
+        protected boolean processMessage(Message message) {
+            log("IncomingHandsfree State -> Processing Message: " + message.what);
+            switch(message.what) {
+                case CONNECT_HFP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case CONNECT_HFP_INCOMING:
+                    // Ignore
+                    Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
+                    break;
+                case CONNECT_A2DP_INCOMING:
+                    // Serialize the commands.
+                    deferMessage(message);
+                    break;
+                case CONNECT_A2DP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_HFP_OUTGOING:
+                    // We don't know at what state we are in the incoming HFP connection state.
+                    // We can be changing from DISCONNECTED to CONNECTING, or
+                    // from CONNECTING to CONNECTED, so serializing this command is
+                    // the safest option.
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_HFP_INCOMING:
+                    // Nothing to do here, we will already be DISCONNECTED
+                    // by this point.
+                    break;
+                case DISCONNECT_A2DP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_A2DP_INCOMING:
+                    // Bluez handles incoming A2DP disconnect.
+                    // If this causes incoming HFP to fail, it is more of a headset problem
+                    // since both connections are incoming ones.
+                    break;
+                case UNPAIR:
+                case AUTO_CONNECT_PROFILES:
+                    deferMessage(message);
+                    break;
+                case TRANSITION_TO_STABLE:
+                    transitionTo(mBondedDevice);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    private class OutgoingA2dp extends HierarchicalState {
+        private boolean mStatus = false;
+        private int mCommand;
+
+        @Override
+        protected void enter() {
+            log("Entering OutgoingA2dp state with: " + getCurrentMessage().what);
+            mCommand = getCurrentMessage().what;
+            if (mCommand != CONNECT_A2DP_OUTGOING &&
+                mCommand != DISCONNECT_A2DP_OUTGOING) {
+                Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
+            }
+            mStatus = processCommand(mCommand);
+            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+        }
+
+        @Override
+        protected boolean processMessage(Message message) {
+            log("OutgoingA2dp State->Processing Message: " + message.what);
+            Message deferMsg = new Message();
+            switch(message.what) {
+                case CONNECT_HFP_OUTGOING:
+                    processCommand(CONNECT_HFP_OUTGOING);
+
+                    // Don't cancel A2DP outgoing as there is no guarantee it
+                    // will get canceled.
+                    // It might already be connected but we might not have got the
+                    // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
+                    // The worst case, the connection will fail, retry.
+                    // The same applies to Disconnecting an A2DP connection.
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case CONNECT_HFP_INCOMING:
+                    processCommand(CONNECT_HFP_INCOMING);
+
+                    // Don't cancel A2DP outgoing as there is no guarantee
+                    // it will get canceled.
+                    // The worst case, the connection will fail, retry.
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case CONNECT_A2DP_INCOMING:
+                    // Bluez will take care of conflicts between incoming and outgoing
+                    // connections.
+                    transitionTo(mIncomingA2dp);
+                    break;
+                case CONNECT_A2DP_OUTGOING:
+                    // Ignore
+                    break;
+                case DISCONNECT_HFP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_HFP_INCOMING:
+                    // At this point, we are already disconnected
+                    // with HFP. Sometimes A2DP connection can
+                    // fail due to the disconnection of HFP. So add a retry
+                    // for the A2DP.
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case DISCONNECT_A2DP_OUTGOING:
+                    processCommand(DISCONNECT_A2DP_OUTGOING);
+                    break;
+                case DISCONNECT_A2DP_INCOMING:
+                    // Ignore, will be handled by Bluez
+                    break;
+                case UNPAIR:
+                case AUTO_CONNECT_PROFILES:
+                    deferMessage(message);
+                    break;
+                case TRANSITION_TO_STABLE:
+                    transitionTo(mBondedDevice);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    private class IncomingA2dp extends HierarchicalState {
+        private boolean mStatus = false;
+        private int mCommand;
+
+        @Override
+        protected void enter() {
+            log("Entering IncomingA2dp state with: " + getCurrentMessage().what);
+            mCommand = getCurrentMessage().what;
+            if (mCommand != CONNECT_A2DP_INCOMING &&
+                mCommand != DISCONNECT_A2DP_INCOMING) {
+                Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
+            }
+            mStatus = processCommand(mCommand);
+            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+        }
+
+        @Override
+        protected boolean processMessage(Message message) {
+            log("IncomingA2dp State->Processing Message: " + message.what);
+            Message deferMsg = new Message();
+            switch(message.what) {
+                case CONNECT_HFP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case CONNECT_HFP_INCOMING:
+                    // Shouldn't happen, but serialize the commands.
+                    deferMessage(message);
+                    break;
+                case CONNECT_A2DP_INCOMING:
+                    // ignore
+                    break;
+                case CONNECT_A2DP_OUTGOING:
+                    // Defer message and retry
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_HFP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_HFP_INCOMING:
+                    // Shouldn't happen but if does, we can handle it.
+                    // Depends if the headset can handle it.
+                    // Incoming A2DP will be handled by Bluez, Disconnect HFP
+                    // the socket would have already been closed.
+                    // ignore
+                    break;
+                case DISCONNECT_A2DP_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT_A2DP_INCOMING:
+                    // Ignore, will be handled by Bluez
+                    break;
+                case UNPAIR:
+                case AUTO_CONNECT_PROFILES:
+                    deferMessage(message);
+                    break;
+                case TRANSITION_TO_STABLE:
+                    transitionTo(mBondedDevice);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+
+
+    synchronized void cancelCommand(int command) {
+        if (command == CONNECT_HFP_OUTGOING ) {
+            // Cancel the outgoing thread.
+            if (mHeadsetServiceConnected) {
+                mHeadsetService.cancelConnectThread();
+            }
+            // HeadsetService is down. Phone process most likely crashed.
+            // The thread would have got killed.
+        }
+    }
+
+    synchronized void deferHeadsetMessage(int command) {
+        Message msg = new Message();
+        msg.what = command;
+        deferMessage(msg);
+    }
+
+    synchronized boolean processCommand(int command) {
+        log("Processing command:" + command);
+        switch(command) {
+            case  CONNECT_HFP_OUTGOING:
+                if (mHeadsetService != null) {
+                    return mHeadsetService.connectHeadsetInternal(mDevice);
+                }
+                break;
+            case CONNECT_HFP_INCOMING:
+                if (!mHeadsetServiceConnected) {
+                    deferHeadsetMessage(command);
+                } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
+                    return mHeadsetService.acceptIncomingConnect(mDevice);
+                } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
+                    return mHeadsetService.createIncomingConnect(mDevice);
+                }
+                break;
+            case CONNECT_A2DP_OUTGOING:
+                if (mA2dpService != null) {
+                    return mA2dpService.connectSinkInternal(mDevice);
+                }
+                break;
+            case CONNECT_A2DP_INCOMING:
+                // ignore, Bluez takes care
+                return true;
+            case DISCONNECT_HFP_OUTGOING:
+                if (!mHeadsetServiceConnected) {
+                    deferHeadsetMessage(command);
+                } else {
+                    if (mHeadsetService.getPriority(mDevice) ==
+                        BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
+                        mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
+                    }
+                    return mHeadsetService.disconnectHeadsetInternal(mDevice);
+                }
+                break;
+            case DISCONNECT_HFP_INCOMING:
+                // ignore
+                return true;
+            case DISCONNECT_A2DP_INCOMING:
+                // ignore
+                return true;
+            case DISCONNECT_A2DP_OUTGOING:
+                if (mA2dpService != null) {
+                    if (mA2dpService.getSinkPriority(mDevice) ==
+                        BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
+                        mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
+                    }
+                    return mA2dpService.disconnectSinkInternal(mDevice);
+                }
+                break;
+            case UNPAIR:
+                return mService.removeBondInternal(mDevice.getAddress());
+            default:
+                Log.e(TAG, "Error: Unknown Command");
+        }
+        return false;
+    }
+
+    private void log(String message) {
+        if (DBG) {
+            Log.i(TAG, "Device:" + mDevice + " Message:" + message);
+        }
+    }
+}
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 @@ 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 168fe3b..40f1058 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 6cccd50..57a9c06 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -34,4 +34,10 @@ interface IBluetoothHeadset {
     boolean setPriority(in BluetoothDevice device, int priority);
     int getPriority(in BluetoothDevice device);
     int getBatteryUsageHint();
+
+    boolean createIncomingConnect(in BluetoothDevice device);
+    boolean acceptIncomingConnect(in BluetoothDevice device);
+    boolean cancelConnectThread();
+    boolean connectHeadsetInternal(in BluetoothDevice device);
+    boolean disconnectHeadsetInternal(in BluetoothDevice device);
 }
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.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 c0e4600..e1d3f13 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 c0affd3..fc40770 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -28,6 +28,7 @@ import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfileConnectionState;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
@@ -112,7 +113,7 @@ public class BluetoothService extends IBluetooth.Stub {
             BluetoothUuid.HSP,
             BluetoothUuid.ObexObjectPush };
 
-
+    // TODO(): Optimize all these string handling
     private final Map<String, String> mAdapterProperties;
     private final HashMap<String, Map<String, String>> mDeviceProperties;
 
@@ -122,6 +123,9 @@ public class BluetoothService extends IBluetooth.Stub {
 
     private final HashMap<Integer, Integer> mServiceRecordToPid;
 
+    private final HashMap<String, BluetoothProfileConnectionState> mProfileConnectionMgr;
+
+    private BluetoothA2dpService mA2dpService;
     private static String mDockAddress;
     private String mDockPin;
 
@@ -179,6 +183,7 @@ public class BluetoothService extends IBluetooth.Stub {
         mUuidIntentTracker = new ArrayList<String>();
         mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
         mServiceRecordToPid = new HashMap<Integer, Integer>();
+        mProfileConnectionMgr = new HashMap<String, BluetoothProfileConnectionState>();
 
         IntentFilter filter = new IntentFilter();
         registerForAirplaneMode(filter);
@@ -187,7 +192,7 @@ public class BluetoothService extends IBluetooth.Stub {
         mContext.registerReceiver(mReceiver, filter);
     }
 
-     public static synchronized String readDockBluetoothAddress() {
+    public static synchronized String readDockBluetoothAddress() {
         if (mDockAddress != null) return mDockAddress;
 
         BufferedInputStream file = null;
@@ -534,6 +539,7 @@ public class BluetoothService extends IBluetooth.Stub {
                 mIsDiscovering = false;
                 mBondState.readAutoPairingData();
                 mBondState.loadBondState();
+                initProfileState();
                 mHandler.sendMessageDelayed(
                         mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
 
@@ -648,6 +654,12 @@ public class BluetoothService extends IBluetooth.Stub {
                 }
             }
 
+            if (state == BluetoothDevice.BOND_BONDED) {
+                addProfileState(address);
+            } else if (state == BluetoothDevice.BOND_NONE) {
+                removeProfileState(address);
+            }
+
             if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
                          reason + ")");
             Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
@@ -1167,6 +1179,16 @@ public class BluetoothService extends IBluetooth.Stub {
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             return false;
         }
+        BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
+        if (state != null) {
+            state.sendMessage(BluetoothProfileConnectionState.UNPAIR);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public synchronized boolean removeBondInternal(String address) {
         return removeDeviceNative(getObjectPathFromAddress(address));
     }
 
@@ -1919,6 +1941,104 @@ public class BluetoothService extends IBluetooth.Stub {
         if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
     }
 
+    public boolean connectHeadset(String address) {
+        BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
+        if (state != null) {
+          state.sendMessage(BluetoothProfileConnectionState.CONNECT_HFP_OUTGOING);
+          return true;
+        }
+        return false;
+    }
+
+    public boolean disconnectHeadset(String address) {
+        BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
+        if (state != null) {
+            state.sendMessage(BluetoothProfileConnectionState.DISCONNECT_HFP_OUTGOING);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean connectSink(String address) {
+        BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
+        if (state != null) {
+          state.sendMessage(BluetoothProfileConnectionState.CONNECT_A2DP_OUTGOING);
+          return true;
+        }
+        return false;
+    }
+
+    public boolean disconnectSink(String address) {
+        BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
+        if (state != null) {
+            state.sendMessage(BluetoothProfileConnectionState.DISCONNECT_A2DP_OUTGOING);
+            return true;
+        }
+        return false;
+    }
+
+    private BluetoothProfileConnectionState addProfileState(String address) {
+        BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
+        if (state != null) return state;
+
+        state = new BluetoothProfileConnectionState(mContext, address, this, mA2dpService);
+        mProfileConnectionMgr.put(address, state);
+        state.start();
+        return state;
+    }
+
+    private void removeProfileState(String address) {
+        mProfileConnectionMgr.remove(address);
+    }
+
+    private void initProfileState() {
+        String []bonds = null;
+        String val = getPropertyInternal("Devices");
+        if (val != null) {
+            bonds = val.split(",");
+        }
+        if (bonds == null) {
+            return;
+        }
+
+        for (String path : bonds) {
+            String address = getAddressFromObjectPath(path);
+            BluetoothProfileConnectionState state = addProfileState(address);
+            // Allow 8 secs for SDP records to get registered.
+            Message msg = new Message();
+            msg.what = BluetoothProfileConnectionState.AUTO_CONNECT_PROFILES;
+            state.sendMessageDelayed(msg, 8000);
+        }
+    }
+
+    public boolean notifyIncomingConnection(String address) {
+        BluetoothProfileConnectionState state =
+             mProfileConnectionMgr.get(address);
+        if (state != null) {
+            Message msg = new Message();
+            msg.what = BluetoothProfileConnectionState.CONNECT_HFP_INCOMING;
+            state.sendMessage(msg);
+            return true;
+        }
+        return false;
+    }
+
+    /*package*/ boolean notifyIncomingA2dpConnection(String address) {
+       BluetoothProfileConnectionState state =
+            mProfileConnectionMgr.get(address);
+       if (state != null) {
+           Message msg = new Message();
+           msg.what = BluetoothProfileConnectionState.CONNECT_A2DP_INCOMING;
+           state.sendMessage(msg);
+           return true;
+       }
+       return false;
+    }
+
+    /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) {
+        mA2dpService = a2dpService;
+    }
+
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
-- 
cgit v1.1