1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
19import android.app.Service;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothAudioGateway;
22import android.bluetooth.BluetoothAudioGateway.IncomingConnectionInfo;
23import android.bluetooth.BluetoothDevice;
24import android.bluetooth.BluetoothHeadset;
25import android.bluetooth.BluetoothProfile;
26import android.bluetooth.BluetoothUuid;
27import android.bluetooth.HeadsetBase;
28import android.bluetooth.IBluetooth;
29import android.bluetooth.IBluetoothHeadset;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.media.AudioManager;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.Message;
38import android.os.ParcelUuid;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.provider.Settings;
43import android.util.Log;
44
45import java.util.ArrayList;
46import java.util.List;
47import java.util.concurrent.ConcurrentHashMap;
48
49/**
50 * Provides Bluetooth Headset and Handsfree profile, as a service in
51 * the Phone application.
52 * @hide
53 */
54public class BluetoothHeadsetService extends Service {
55    private static final String TAG = "Bluetooth HSHFP";
56    private static final boolean DBG = true;
57
58    private static final String PREF_NAME = BluetoothHeadsetService.class.getSimpleName();
59    private static final String PREF_LAST_HEADSET = "lastHeadsetAddress";
60
61    private static final int PHONE_STATE_CHANGED = 1;
62
63    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
64    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
65
66    private static boolean sHasStarted = false;
67
68    private BluetoothDevice mDeviceSdpQuery;
69    private BluetoothAdapter mAdapter;
70    private IBluetooth mBluetoothService;
71    private PowerManager mPowerManager;
72    private BluetoothAudioGateway mAg;
73    private BluetoothHandsfree mBtHandsfree;
74    private ConcurrentHashMap<BluetoothDevice, BluetoothRemoteHeadset> mRemoteHeadsets;
75    private BluetoothDevice mAudioConnectedDevice;
76
77    @Override
78    public void onCreate() {
79        super.onCreate();
80        mAdapter = BluetoothAdapter.getDefaultAdapter();
81        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
82        mBtHandsfree = PhoneApp.getInstance().getBluetoothHandsfree();
83        mAg = new BluetoothAudioGateway(mAdapter);
84        IntentFilter filter = new IntentFilter(
85                BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
86        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
87        filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
88        filter.addAction(BluetoothDevice.ACTION_UUID);
89        registerReceiver(mBluetoothReceiver, filter);
90
91        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
92        if (b == null) {
93            throw new RuntimeException("Bluetooth service not available");
94        }
95        mBluetoothService = IBluetooth.Stub.asInterface(b);
96        mRemoteHeadsets = new ConcurrentHashMap<BluetoothDevice, BluetoothRemoteHeadset>();
97   }
98
99   private class BluetoothRemoteHeadset {
100       private int mState;
101       private int mAudioState;
102       private int mHeadsetType;
103       private HeadsetBase mHeadset;
104       private IncomingConnectionInfo mIncomingInfo;
105
106       BluetoothRemoteHeadset() {
107           mState = BluetoothProfile.STATE_DISCONNECTED;
108           mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
109           mHeadset = null;
110           mIncomingInfo = null;
111           mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
112       }
113
114       BluetoothRemoteHeadset(int headsetType, IncomingConnectionInfo incomingInfo) {
115           mState = BluetoothProfile.STATE_DISCONNECTED;
116           mHeadsetType = headsetType;
117           mHeadset = null;
118           mIncomingInfo = incomingInfo;
119           mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
120       }
121   }
122
123   synchronized private BluetoothDevice getCurrentDevice() {
124       for (BluetoothDevice device : mRemoteHeadsets.keySet()) {
125           int state = mRemoteHeadsets.get(device).mState;
126           if (state == BluetoothProfile.STATE_CONNECTING ||
127               state == BluetoothProfile.STATE_CONNECTED) {
128               return device;
129           }
130       }
131       return null;
132   }
133
134    @Override
135    public void onStart(Intent intent, int startId) {
136         if (mAdapter == null) {
137            Log.w(TAG, "Stopping BluetoothHeadsetService: device does not have BT");
138            stopSelf();
139        } else {
140            if (!sHasStarted) {
141                if (DBG) log("Starting BluetoothHeadsetService");
142                if (mAdapter.isEnabled()) {
143                    mAg.start(mIncomingConnectionHandler);
144                    mBtHandsfree.onBluetoothEnabled();
145                }
146                sHasStarted = true;
147            }
148        }
149    }
150
151    private final Handler mIncomingConnectionHandler = new Handler() {
152        @Override
153        public void handleMessage(Message msg) {
154            synchronized(BluetoothHeadsetService.this) {
155                IncomingConnectionInfo info = (IncomingConnectionInfo)msg.obj;
156                int type = BluetoothHandsfree.TYPE_UNKNOWN;
157                switch(msg.what) {
158                case BluetoothAudioGateway.MSG_INCOMING_HEADSET_CONNECTION:
159                    type = BluetoothHandsfree.TYPE_HEADSET;
160                    break;
161                case BluetoothAudioGateway.MSG_INCOMING_HANDSFREE_CONNECTION:
162                    type = BluetoothHandsfree.TYPE_HANDSFREE;
163                    break;
164                }
165
166                Log.i(TAG, "Incoming rfcomm (" + BluetoothHandsfree.typeToString(type) +
167                      ") connection from " + info.mRemoteDevice + "on channel " +
168                      info.mRfcommChan);
169
170                int priority = BluetoothProfile.PRIORITY_OFF;
171                HeadsetBase headset;
172                priority = getPriority(info.mRemoteDevice);
173                if (priority <= BluetoothProfile.PRIORITY_OFF) {
174                    Log.i(TAG, "Rejecting incoming connection because priority = " + priority);
175
176                    headset = new HeadsetBase(mPowerManager, mAdapter,
177                                              info.mRemoteDevice,
178                                              info.mSocketFd, info.mRfcommChan,
179                                              null);
180                    headset.disconnect();
181                    try {
182                        mBluetoothService.notifyIncomingConnection(info.mRemoteDevice.getAddress(),
183                                                                   true);
184                    } catch (RemoteException e) {
185                        Log.e(TAG, "notifyIncomingConnection", e);
186                    }
187                    return;
188                }
189
190                BluetoothRemoteHeadset remoteHeadset;
191                BluetoothDevice device = getCurrentDevice();
192
193                int state = BluetoothProfile.STATE_DISCONNECTED;
194                if (device != null) {
195                    state = mRemoteHeadsets.get(device).mState;
196                }
197
198                switch (state) {
199                case BluetoothProfile.STATE_DISCONNECTED:
200                    // headset connecting us, lets join
201                    remoteHeadset = new BluetoothRemoteHeadset(type, info);
202                    mRemoteHeadsets.put(info.mRemoteDevice, remoteHeadset);
203
204                    try {
205                        mBluetoothService.notifyIncomingConnection(
206                           info.mRemoteDevice.getAddress(), false);
207                    } catch (RemoteException e) {
208                        Log.e(TAG, "notifyIncomingConnection");
209                    }
210                    break;
211                case BluetoothProfile.STATE_CONNECTING:
212                    if (!info.mRemoteDevice.equals(device)) {
213                        // different headset, ignoring
214                        Log.i(TAG, "Already attempting connect to " + device +
215                              ", disconnecting " + info.mRemoteDevice);
216
217                        headset = new HeadsetBase(mPowerManager, mAdapter,
218                                                  info.mRemoteDevice,
219                                                  info.mSocketFd, info.mRfcommChan,
220                                                  null);
221                        headset.disconnect();
222                        break;
223                    }
224
225                    // Incoming and Outgoing connections to the same headset.
226                    // The state machine manager will cancel outgoing and accept the incoming one.
227                    // Update the state
228                    mRemoteHeadsets.get(info.mRemoteDevice).mHeadsetType = type;
229                    mRemoteHeadsets.get(info.mRemoteDevice).mIncomingInfo = info;
230
231                    try {
232                        mBluetoothService.notifyIncomingConnection(
233                            info.mRemoteDevice.getAddress(), false);
234                    } catch (RemoteException e) {
235                        Log.e(TAG, "notifyIncomingConnection");
236                    }
237                    break;
238                case BluetoothProfile.STATE_CONNECTED:
239                    Log.i(TAG, "Already connected to " + device + ", disconnecting " +
240                            info.mRemoteDevice);
241                    rejectIncomingConnection(info);
242                    break;
243                }
244            }
245        }
246    };
247
248    private void rejectIncomingConnection(IncomingConnectionInfo info) {
249        HeadsetBase headset = new HeadsetBase(mPowerManager, mAdapter,
250            info.mRemoteDevice, info.mSocketFd, info.mRfcommChan, null);
251        headset.disconnect();
252    }
253
254
255    private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
256
257        @Override
258        public void onReceive(Context context, Intent intent) {
259            String action = intent.getAction();
260            BluetoothDevice device =
261                    intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
262
263            BluetoothDevice currDevice = getCurrentDevice();
264            int state = BluetoothProfile.STATE_DISCONNECTED;
265            if (currDevice != null) {
266                state = mRemoteHeadsets.get(currDevice).mState;
267            }
268
269            if ((state == BluetoothProfile.STATE_CONNECTED ||
270                    state == BluetoothProfile.STATE_CONNECTING) &&
271                    action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) &&
272                    device.equals(currDevice)) {
273                try {
274                    mBinder.disconnect(currDevice);
275                } catch (RemoteException e) {}
276            } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
277                switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
278                                           BluetoothAdapter.ERROR)) {
279                case BluetoothAdapter.STATE_ON:
280                    mAg.start(mIncomingConnectionHandler);
281                    mBtHandsfree.onBluetoothEnabled();
282                    break;
283                case BluetoothAdapter.STATE_TURNING_OFF:
284                    mBtHandsfree.onBluetoothDisabled();
285                    mAg.stop();
286                    if (currDevice != null) {
287                        try {
288                            mBinder.disconnect(currDevice);
289                        } catch (RemoteException e) {}
290                    }
291                    break;
292                }
293            } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
294                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
295                if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
296                    mBtHandsfree.sendScoGainUpdate(intent.getIntExtra(
297                            AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0));
298                }
299
300            } else if (action.equals(BluetoothDevice.ACTION_UUID)) {
301                if (device.equals(mDeviceSdpQuery) && device.equals(currDevice)) {
302                    // We have got SDP records for the device we are interested in.
303                    getSdpRecordsAndConnect(device);
304                }
305            }
306        }
307    };
308
309    private static final int CONNECT_HEADSET_DELAYED = 1;
310    private Handler mHandler = new Handler() {
311        @Override
312        public void handleMessage(Message msg) {
313            switch (msg.what) {
314                case CONNECT_HEADSET_DELAYED:
315                    BluetoothDevice device = (BluetoothDevice) msg.obj;
316                    getSdpRecordsAndConnect(device);
317                    break;
318            }
319        }
320    };
321
322    @Override
323    public IBinder onBind(Intent intent) {
324        return mBinder;
325    }
326
327    // ------------------------------------------------------------------
328    // Bluetooth Headset Connect
329    // ------------------------------------------------------------------
330    private static final int RFCOMM_CONNECTED             = 1;
331    private static final int RFCOMM_ERROR                 = 2;
332
333    private long mTimestamp;
334
335    /**
336     * Thread for RFCOMM connection
337     * Messages are sent to mConnectingStatusHandler as connection progresses.
338     */
339    private RfcommConnectThread mConnectThread;
340    private class RfcommConnectThread extends Thread {
341        private BluetoothDevice device;
342        private int channel;
343        private int type;
344
345        private static final int EINTERRUPT = -1000;
346        private static final int ECONNREFUSED = -111;
347
348        public RfcommConnectThread(BluetoothDevice device, int channel, int type) {
349            super();
350            this.device = device;
351            this.channel = channel;
352            this.type = type;
353        }
354
355        private int waitForConnect(HeadsetBase headset) {
356            // Try to connect for 20 seconds
357            int result = 0;
358            for (int i=0; i < 40 && result == 0; i++) {
359                // waitForAsyncConnect returns 0 on timeout, 1 on success, < 0 on error.
360                result = headset.waitForAsyncConnect(500, mConnectedStatusHandler);
361                if (isInterrupted()) {
362                    headset.disconnect();
363                    return EINTERRUPT;
364                }
365            }
366            return result;
367        }
368
369        @Override
370        public void run() {
371            long timestamp;
372
373            timestamp = System.currentTimeMillis();
374            HeadsetBase headset = new HeadsetBase(mPowerManager, mAdapter,
375                                                  device, channel);
376
377            int result = waitForConnect(headset);
378
379            if (result != EINTERRUPT && result != 1) {
380                if (result == ECONNREFUSED && mDeviceSdpQuery == null) {
381                    // The rfcomm channel number might have changed, do SDP
382                    // query and try to connect again.
383                    mDeviceSdpQuery = getCurrentDevice();
384                    device.fetchUuidsWithSdp();
385                    mConnectThread = null;
386                    return;
387                } else {
388                    Log.i(TAG, "Trying to connect to rfcomm socket again after 1 sec");
389                    try {
390                      sleep(1000);  // 1 second
391                    } catch (InterruptedException e) {
392                      return;
393                    }
394                }
395                result = waitForConnect(headset);
396            }
397            mDeviceSdpQuery = null;
398            if (result == EINTERRUPT) return;
399
400            if (DBG) log("RFCOMM connection attempt took " +
401                  (System.currentTimeMillis() - timestamp) + " ms");
402            if (isInterrupted()) {
403                headset.disconnect();
404                return;
405            }
406            if (result < 0) {
407                Log.w(TAG, "headset.waitForAsyncConnect() error: " + result);
408                mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
409                return;
410            } else if (result == 0) {
411                mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
412                Log.w(TAG, "mHeadset.waitForAsyncConnect() error: " + result + "(timeout)");
413                return;
414            } else {
415                mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
416            }
417        }
418    }
419
420    /**
421     * Receives events from mConnectThread back in the main thread.
422     */
423    private final Handler mConnectingStatusHandler = new Handler() {
424        @Override
425        public void handleMessage(Message msg) {
426            BluetoothDevice device = getCurrentDevice();
427            if (device == null ||
428                mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTING) {
429                return;  // stale events
430            }
431
432            switch (msg.what) {
433            case RFCOMM_ERROR:
434                if (DBG) log("Rfcomm error");
435                mConnectThread = null;
436                setState(device, BluetoothProfile.STATE_DISCONNECTED);
437                break;
438            case RFCOMM_CONNECTED:
439                if (DBG) log("Rfcomm connected");
440                mConnectThread = null;
441                HeadsetBase headset = (HeadsetBase)msg.obj;
442                setState(device, BluetoothProfile.STATE_CONNECTED);
443
444                mRemoteHeadsets.get(device).mHeadset = headset;
445                mBtHandsfree.connectHeadset(headset, mRemoteHeadsets.get(device).mHeadsetType);
446                break;
447            }
448        }
449    };
450
451    /**
452     * Receives events from a connected RFCOMM socket back in the main thread.
453     */
454    private final Handler mConnectedStatusHandler = new Handler() {
455        @Override
456        public void handleMessage(Message msg) {
457            switch (msg.what) {
458            case HeadsetBase.RFCOMM_DISCONNECTED:
459                mBtHandsfree.resetAtState();
460                mBtHandsfree.setVirtualCallInProgress(false);
461                BluetoothDevice device = getCurrentDevice();
462                if (device != null) {
463                    setState(device, BluetoothProfile.STATE_DISCONNECTED);
464                }
465                break;
466            }
467        }
468    };
469
470    private synchronized void setState(BluetoothDevice device, int state) {
471        int prevState = mRemoteHeadsets.get(device).mState;
472        if (state != prevState) {
473            if (DBG) log("Device: " + device +
474                " Headset  state" + prevState + " -> " + state);
475            if (prevState == BluetoothProfile.STATE_CONNECTED) {
476                // Headset is disconnecting, stop the parser.
477                mBtHandsfree.disconnectHeadset();
478            }
479            Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
480            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
481            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
482            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
483            if (state == BluetoothProfile.STATE_DISCONNECTED) {
484                mRemoteHeadsets.get(device).mHeadset = null;
485                mRemoteHeadsets.get(device).mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
486            }
487
488            mRemoteHeadsets.get(device).mState = state;
489
490            sendBroadcast(intent, BLUETOOTH_PERM);
491            if (state == BluetoothHeadset.STATE_CONNECTED) {
492                // Set the priority to AUTO_CONNECT
493                setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
494                adjustOtherHeadsetPriorities(device);
495            }
496            try {
497                mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEADSET,
498                                                            state, prevState);
499            } catch (RemoteException e) {
500                Log.e(TAG, "sendConnectionStateChange: exception");
501            }
502       }
503    }
504
505    private void adjustOtherHeadsetPriorities(BluetoothDevice connectedDevice) {
506       for (BluetoothDevice device : mAdapter.getBondedDevices()) {
507          if (getPriority(device) >= BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
508              !device.equals(connectedDevice)) {
509              setPriority(device, BluetoothHeadset.PRIORITY_ON);
510          }
511       }
512    }
513
514    private void setPriority(BluetoothDevice device, int priority) {
515        try {
516            mBinder.setPriority(device, priority);
517        } catch (RemoteException e) {
518            Log.e(TAG, "Error while setting priority for: " + device);
519        }
520    }
521
522    private int getPriority(BluetoothDevice device) {
523        try {
524            return mBinder.getPriority(device);
525        } catch (RemoteException e) {
526            Log.e(TAG, "Error while getting priority for: " + device);
527        }
528        return BluetoothProfile.PRIORITY_UNDEFINED;
529    }
530
531    private synchronized void getSdpRecordsAndConnect(BluetoothDevice device) {
532        if (!device.equals(getCurrentDevice())) {
533            // stale
534            return;
535        }
536
537        // Check if incoming connection has already connected.
538        if (mRemoteHeadsets.get(device).mState == BluetoothProfile.STATE_CONNECTED) {
539            return;
540        }
541
542        ParcelUuid[] uuids = device.getUuids();
543        ParcelUuid[] localUuids = mAdapter.getUuids();
544        int type = BluetoothHandsfree.TYPE_UNKNOWN;
545        if (uuids != null) {
546            if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree) &&
547                BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG)) {
548                log("SDP UUID: TYPE_HANDSFREE");
549                type = BluetoothHandsfree.TYPE_HANDSFREE;
550                mRemoteHeadsets.get(device).mHeadsetType = type;
551                int channel = device.getServiceChannel(BluetoothUuid.Handsfree);
552                mConnectThread = new RfcommConnectThread(device, channel, type);
553                if (mAdapter.isDiscovering()) {
554                    mAdapter.cancelDiscovery();
555                }
556                mConnectThread.start();
557                if (getPriority(device) < BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
558                    setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
559                }
560                return;
561            } else if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) &&
562                BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG)) {
563                log("SDP UUID: TYPE_HEADSET");
564                type = BluetoothHandsfree.TYPE_HEADSET;
565                mRemoteHeadsets.get(device).mHeadsetType = type;
566                int channel = device.getServiceChannel(BluetoothUuid.HSP);
567                mConnectThread = new RfcommConnectThread(device, channel, type);
568                if (mAdapter.isDiscovering()) {
569                    mAdapter.cancelDiscovery();
570                }
571                mConnectThread.start();
572                if (getPriority(device) < BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
573                    setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
574                }
575                return;
576            }
577        }
578        log("SDP UUID: TYPE_UNKNOWN");
579        mRemoteHeadsets.get(device).mHeadsetType = type;
580        setState(device, BluetoothProfile.STATE_DISCONNECTED);
581        return;
582    }
583
584    /**
585     * Handlers for incoming service calls
586     */
587    private final IBluetoothHeadset.Stub mBinder = new IBluetoothHeadset.Stub() {
588        public int getConnectionState(BluetoothDevice device) {
589            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
590            BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
591            if (headset == null) {
592                return BluetoothProfile.STATE_DISCONNECTED;
593            }
594            return headset.mState;
595        }
596
597        public List<BluetoothDevice> getConnectedDevices() {
598            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
599            return getDevicesMatchingConnectionStates(
600                new int[] {BluetoothProfile.STATE_CONNECTED});
601        }
602
603        public boolean connect(BluetoothDevice device) {
604            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
605                                           "Need BLUETOOTH_ADMIN permission");
606            synchronized (BluetoothHeadsetService.this) {
607                BluetoothDevice currDevice = getCurrentDevice();
608
609                if (currDevice == device ||
610                    getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
611                    return false;
612                }
613                if (currDevice != null) {
614                    disconnect(currDevice);
615                }
616                try {
617                    return mBluetoothService.connectHeadset(device.getAddress());
618                } catch (RemoteException e) {
619                    Log.e(TAG, "connectHeadset");
620                    return false;
621                }
622            }
623        }
624
625        public boolean disconnect(BluetoothDevice device) {
626            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
627                                           "Need BLUETOOTH_ADMIN permission");
628            synchronized (BluetoothHeadsetService.this) {
629                BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
630                if (headset == null ||
631                    headset.mState == BluetoothProfile.STATE_DISCONNECTED ||
632                    headset.mState == BluetoothProfile.STATE_DISCONNECTING) {
633                    return false;
634                }
635                try {
636                    return mBluetoothService.disconnectHeadset(device.getAddress());
637                } catch (RemoteException e) {
638                    Log.e(TAG, "disconnectHeadset");
639                    return false;
640                }
641            }
642        }
643
644        public synchronized boolean isAudioConnected(BluetoothDevice device) {
645            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
646            if (device.equals(mAudioConnectedDevice)) return true;
647            return false;
648        }
649
650        public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
651            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
652            List<BluetoothDevice> headsets = new ArrayList<BluetoothDevice>();
653            for (BluetoothDevice device: mRemoteHeadsets.keySet()) {
654                int headsetState = getConnectionState(device);
655                for (int state : states) {
656                    if (state == headsetState) {
657                        headsets.add(device);
658                        break;
659                    }
660                }
661            }
662            return headsets;
663        }
664
665        public boolean startVoiceRecognition(BluetoothDevice device) {
666            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
667            synchronized (BluetoothHeadsetService.this) {
668                if (device == null ||
669                    mRemoteHeadsets.get(device) == null ||
670                    mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED) {
671                    return false;
672                }
673                return mBtHandsfree.startVoiceRecognition();
674            }
675        }
676
677        public boolean stopVoiceRecognition(BluetoothDevice device) {
678            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
679            synchronized (BluetoothHeadsetService.this) {
680                if (device == null ||
681                    mRemoteHeadsets.get(device) == null ||
682                    mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED) {
683                    return false;
684                }
685
686                return mBtHandsfree.stopVoiceRecognition();
687            }
688        }
689
690        public int getBatteryUsageHint(BluetoothDevice device) {
691            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
692
693            return HeadsetBase.getAtInputCount();
694        }
695
696        public int getPriority(BluetoothDevice device) {
697            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
698                "Need BLUETOOTH_ADMIN permission");
699            synchronized (BluetoothHeadsetService.this) {
700                int priority = Settings.Secure.getInt(getContentResolver(),
701                        Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()),
702                        BluetoothProfile.PRIORITY_UNDEFINED);
703                return priority;
704            }
705        }
706
707        public boolean setPriority(BluetoothDevice device, int priority) {
708            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
709                "Need BLUETOOTH_ADMIN permission");
710            synchronized (BluetoothHeadsetService.this) {
711                Settings.Secure.putInt(getContentResolver(),
712                        Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()),
713                        priority);
714                if (DBG) log("Saved priority " + device + " = " + priority);
715                return true;
716            }
717        }
718
719        public boolean createIncomingConnect(BluetoothDevice device) {
720            synchronized (BluetoothHeadsetService.this) {
721                HeadsetBase headset;
722                setState(device, BluetoothProfile.STATE_CONNECTING);
723
724                IncomingConnectionInfo info = mRemoteHeadsets.get(device).mIncomingInfo;
725                headset = new HeadsetBase(mPowerManager, mAdapter,
726                                          device,
727                                          info.mSocketFd, info.mRfcommChan,
728                                          mConnectedStatusHandler);
729
730                mRemoteHeadsets.get(device).mHeadset = headset;
731
732                mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
733                return true;
734            }
735        }
736
737        public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
738            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
739            synchronized (BluetoothHeadsetService.this) {
740                if (device == null ||
741                    mRemoteHeadsets.get(device) == null ||
742                    mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED ||
743                    getAudioState(device) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
744                    return false;
745                }
746                return mBtHandsfree.initiateScoUsingVirtualVoiceCall();
747            }
748        }
749
750        public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
751            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
752            synchronized (BluetoothHeadsetService.this) {
753                if (device == null ||
754                    mRemoteHeadsets.get(device) == null ||
755                    mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED ||
756                    getAudioState(device) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
757                    return false;
758                }
759                return mBtHandsfree.terminateScoUsingVirtualVoiceCall();
760            }
761        }
762
763        public boolean rejectIncomingConnect(BluetoothDevice device) {
764            synchronized (BluetoothHeadsetService.this) {
765                BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
766                if (headset != null) {
767                    IncomingConnectionInfo info = headset.mIncomingInfo;
768                    rejectIncomingConnection(info);
769                } else {
770                    Log.e(TAG, "Error no record of remote headset");
771                }
772                return true;
773            }
774        }
775
776        public boolean acceptIncomingConnect(BluetoothDevice device) {
777            synchronized (BluetoothHeadsetService.this) {
778                HeadsetBase headset;
779                BluetoothRemoteHeadset cachedHeadset = mRemoteHeadsets.get(device);
780                if (cachedHeadset == null) {
781                    Log.e(TAG, "Cached Headset is Null in acceptIncomingConnect");
782                    return false;
783                }
784                IncomingConnectionInfo info = cachedHeadset.mIncomingInfo;
785                headset = new HeadsetBase(mPowerManager, mAdapter,
786                                          device,
787                                          info.mSocketFd, info.mRfcommChan,
788                                          mConnectedStatusHandler);
789
790                setState(device, BluetoothProfile.STATE_CONNECTED);
791
792                cachedHeadset.mHeadset = headset;
793                mBtHandsfree.connectHeadset(headset, cachedHeadset.mHeadsetType);
794
795                if (DBG) log("Successfully used incoming connection");
796                return true;
797            }
798        }
799
800        public  boolean cancelConnectThread() {
801            synchronized (BluetoothHeadsetService.this) {
802                if (mConnectThread != null) {
803                    // cancel the connection thread
804                    mConnectThread.interrupt();
805                    try {
806                        mConnectThread.join();
807                    } catch (InterruptedException e) {
808                        Log.e(TAG, "Connection cancelled twice?", e);
809                    }
810                    mConnectThread = null;
811                }
812                return true;
813            }
814        }
815
816        public boolean connectHeadsetInternal(BluetoothDevice device) {
817            synchronized (BluetoothHeadsetService.this) {
818                BluetoothDevice currDevice = getCurrentDevice();
819                if (currDevice == null) {
820                    BluetoothRemoteHeadset headset = new BluetoothRemoteHeadset();
821                    mRemoteHeadsets.put(device, headset);
822
823                    setState(device, BluetoothProfile.STATE_CONNECTING);
824                    if (device.getUuids() == null) {
825                        // We might not have got the UUID change notification from
826                        // Bluez yet, if we have just paired. Try after 1.5 secs.
827                        Message msg = new Message();
828                        msg.what = CONNECT_HEADSET_DELAYED;
829                        msg.obj = device;
830                        mHandler.sendMessageDelayed(msg, 1500);
831                    } else {
832                        getSdpRecordsAndConnect(device);
833                    }
834                    return true;
835                } else {
836                      Log.w(TAG, "connectHeadset(" + device + "): failed: already in state " +
837                            mRemoteHeadsets.get(currDevice).mState +
838                            " with headset " + currDevice);
839                }
840                return false;
841            }
842        }
843
844        public boolean disconnectHeadsetInternal(BluetoothDevice device) {
845            synchronized (BluetoothHeadsetService.this) {
846                BluetoothRemoteHeadset remoteHeadset = mRemoteHeadsets.get(device);
847                if (remoteHeadset == null) return false;
848
849                if (remoteHeadset.mState == BluetoothProfile.STATE_CONNECTED) {
850                    // Send a dummy battery level message to force headset
851                    // out of sniff mode so that it will immediately notice
852                    // the disconnection. We are currently sending it for
853                    // handsfree only.
854                    // TODO: Call hci_conn_enter_active_mode() from
855                    // rfcomm_send_disc() in the kernel instead.
856                    // See http://b/1716887
857                    setState(device, BluetoothProfile.STATE_DISCONNECTING);
858
859                    HeadsetBase headset = remoteHeadset.mHeadset;
860                    if (remoteHeadset.mHeadsetType == BluetoothHandsfree.TYPE_HANDSFREE) {
861                        headset.sendURC("+CIEV: 7,3");
862                    }
863
864                    if (headset != null) {
865                        headset.disconnect();
866                        headset = null;
867                    }
868                    setState(device, BluetoothProfile.STATE_DISCONNECTED);
869                    return true;
870                } else if (remoteHeadset.mState == BluetoothProfile.STATE_CONNECTING) {
871                    // The state machine would have canceled the connect thread.
872                    // Just set the state here.
873                    setState(device, BluetoothProfile.STATE_DISCONNECTED);
874                    return true;
875                }
876                return false;
877            }
878        }
879
880        public boolean setAudioState(BluetoothDevice device, int state) {
881            // mRemoteHeadsets handles put/get concurrency by itself
882            int prevState = mRemoteHeadsets.get(device).mAudioState;
883            mRemoteHeadsets.get(device).mAudioState = state;
884            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
885                mAudioConnectedDevice = device;
886            } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
887                mAudioConnectedDevice = null;
888            }
889            Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
890            intent.putExtra(BluetoothHeadset.EXTRA_STATE, state);
891            intent.putExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, prevState);
892            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
893            sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
894            if (DBG) log("AudioStateIntent: " + device + " State: " + state
895                         + " PrevState: " + prevState);
896            return true;
897        }
898
899        public int getAudioState(BluetoothDevice device) {
900            // mRemoteHeadsets handles put/get concurrency by itself
901            BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
902            if (headset == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
903
904            return headset.mAudioState;
905       }
906    };
907
908    @Override
909    public void onDestroy() {
910        super.onDestroy();
911        if (DBG) log("Stopping BluetoothHeadsetService");
912        unregisterReceiver(mBluetoothReceiver);
913        mBtHandsfree.onBluetoothDisabled();
914        mAg.stop();
915        sHasStarted = false;
916        BluetoothDevice device = getCurrentDevice();
917        if (device != null) {
918            setState(device, BluetoothProfile.STATE_DISCONNECTED);
919        }
920    }
921
922
923
924    private static void log(String msg) {
925        Log.d(TAG, msg);
926    }
927}
928