| /* |
| * Copyright (C) 2006 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 com.android.phone; |
| |
| import android.app.Service; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothAudioGateway; |
| import android.bluetooth.BluetoothAudioGateway.IncomingConnectionInfo; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHeadset; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothUuid; |
| import android.bluetooth.HeadsetBase; |
| import android.bluetooth.IBluetooth; |
| import android.bluetooth.IBluetoothHeadset; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.media.AudioManager; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.ParcelUuid; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.provider.Settings; |
| import android.util.Log; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * Provides Bluetooth Headset and Handsfree profile, as a service in |
| * the Phone application. |
| * @hide |
| */ |
| public class BluetoothHeadsetService extends Service { |
| private static final String TAG = "Bluetooth HSHFP"; |
| private static final boolean DBG = true; |
| |
| private static final String PREF_NAME = BluetoothHeadsetService.class.getSimpleName(); |
| private static final String PREF_LAST_HEADSET = "lastHeadsetAddress"; |
| |
| private static final int PHONE_STATE_CHANGED = 1; |
| |
| private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; |
| private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; |
| |
| private static boolean sHasStarted = false; |
| |
| private BluetoothDevice mDeviceSdpQuery; |
| private BluetoothAdapter mAdapter; |
| private IBluetooth mBluetoothService; |
| private PowerManager mPowerManager; |
| private BluetoothAudioGateway mAg; |
| private BluetoothHandsfree mBtHandsfree; |
| private ConcurrentHashMap<BluetoothDevice, BluetoothRemoteHeadset> mRemoteHeadsets; |
| private BluetoothDevice mAudioConnectedDevice; |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); |
| mBtHandsfree = PhoneApp.getInstance().getBluetoothHandsfree(); |
| mAg = new BluetoothAudioGateway(mAdapter); |
| IntentFilter filter = new IntentFilter( |
| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); |
| filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); |
| filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); |
| filter.addAction(BluetoothDevice.ACTION_UUID); |
| registerReceiver(mBluetoothReceiver, filter); |
| |
| IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); |
| if (b == null) { |
| throw new RuntimeException("Bluetooth service not available"); |
| } |
| mBluetoothService = IBluetooth.Stub.asInterface(b); |
| mRemoteHeadsets = new ConcurrentHashMap<BluetoothDevice, BluetoothRemoteHeadset>(); |
| } |
| |
| private class BluetoothRemoteHeadset { |
| private int mState; |
| private int mAudioState; |
| private int mHeadsetType; |
| private HeadsetBase mHeadset; |
| private IncomingConnectionInfo mIncomingInfo; |
| |
| BluetoothRemoteHeadset() { |
| mState = BluetoothProfile.STATE_DISCONNECTED; |
| mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN; |
| mHeadset = null; |
| mIncomingInfo = null; |
| mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; |
| } |
| |
| BluetoothRemoteHeadset(int headsetType, IncomingConnectionInfo incomingInfo) { |
| mState = BluetoothProfile.STATE_DISCONNECTED; |
| mHeadsetType = headsetType; |
| mHeadset = null; |
| mIncomingInfo = incomingInfo; |
| mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; |
| } |
| } |
| |
| synchronized private BluetoothDevice getCurrentDevice() { |
| for (BluetoothDevice device : mRemoteHeadsets.keySet()) { |
| int state = mRemoteHeadsets.get(device).mState; |
| if (state == BluetoothProfile.STATE_CONNECTING || |
| state == BluetoothProfile.STATE_CONNECTED) { |
| return device; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void onStart(Intent intent, int startId) { |
| if (mAdapter == null) { |
| Log.w(TAG, "Stopping BluetoothHeadsetService: device does not have BT"); |
| stopSelf(); |
| } else { |
| if (!sHasStarted) { |
| if (DBG) log("Starting BluetoothHeadsetService"); |
| if (mAdapter.isEnabled()) { |
| mAg.start(mIncomingConnectionHandler); |
| mBtHandsfree.onBluetoothEnabled(); |
| } |
| sHasStarted = true; |
| } |
| } |
| } |
| |
| private final Handler mIncomingConnectionHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| synchronized(BluetoothHeadsetService.this) { |
| IncomingConnectionInfo info = (IncomingConnectionInfo)msg.obj; |
| int type = BluetoothHandsfree.TYPE_UNKNOWN; |
| switch(msg.what) { |
| case BluetoothAudioGateway.MSG_INCOMING_HEADSET_CONNECTION: |
| type = BluetoothHandsfree.TYPE_HEADSET; |
| break; |
| case BluetoothAudioGateway.MSG_INCOMING_HANDSFREE_CONNECTION: |
| type = BluetoothHandsfree.TYPE_HANDSFREE; |
| break; |
| } |
| |
| Log.i(TAG, "Incoming rfcomm (" + BluetoothHandsfree.typeToString(type) + |
| ") connection from " + info.mRemoteDevice + "on channel " + |
| info.mRfcommChan); |
| |
| int priority = BluetoothProfile.PRIORITY_OFF; |
| HeadsetBase headset; |
| priority = getPriority(info.mRemoteDevice); |
| if (priority <= BluetoothProfile.PRIORITY_OFF) { |
| Log.i(TAG, "Rejecting incoming connection because priority = " + priority); |
| |
| headset = new HeadsetBase(mPowerManager, mAdapter, |
| info.mRemoteDevice, |
| info.mSocketFd, info.mRfcommChan, |
| null); |
| headset.disconnect(); |
| try { |
| mBluetoothService.notifyIncomingConnection(info.mRemoteDevice.getAddress(), |
| true); |
| } catch (RemoteException e) { |
| Log.e(TAG, "notifyIncomingConnection", e); |
| } |
| return; |
| } |
| |
| BluetoothRemoteHeadset remoteHeadset; |
| BluetoothDevice device = getCurrentDevice(); |
| |
| int state = BluetoothProfile.STATE_DISCONNECTED; |
| if (device != null) { |
| state = mRemoteHeadsets.get(device).mState; |
| } |
| |
| switch (state) { |
| case BluetoothProfile.STATE_DISCONNECTED: |
| // headset connecting us, lets join |
| remoteHeadset = new BluetoothRemoteHeadset(type, info); |
| mRemoteHeadsets.put(info.mRemoteDevice, remoteHeadset); |
| |
| try { |
| mBluetoothService.notifyIncomingConnection( |
| info.mRemoteDevice.getAddress(), false); |
| } catch (RemoteException e) { |
| Log.e(TAG, "notifyIncomingConnection"); |
| } |
| break; |
| case BluetoothProfile.STATE_CONNECTING: |
| if (!info.mRemoteDevice.equals(device)) { |
| // different headset, ignoring |
| Log.i(TAG, "Already attempting connect to " + device + |
| ", disconnecting " + info.mRemoteDevice); |
| |
| headset = new HeadsetBase(mPowerManager, mAdapter, |
| info.mRemoteDevice, |
| info.mSocketFd, info.mRfcommChan, |
| null); |
| headset.disconnect(); |
| break; |
| } |
| |
| // Incoming and Outgoing connections to the same headset. |
| // The state machine manager will cancel outgoing and accept the incoming one. |
| // Update the state |
| mRemoteHeadsets.get(info.mRemoteDevice).mHeadsetType = type; |
| mRemoteHeadsets.get(info.mRemoteDevice).mIncomingInfo = info; |
| |
| try { |
| mBluetoothService.notifyIncomingConnection( |
| info.mRemoteDevice.getAddress(), false); |
| } catch (RemoteException e) { |
| Log.e(TAG, "notifyIncomingConnection"); |
| } |
| break; |
| case BluetoothProfile.STATE_CONNECTED: |
| Log.i(TAG, "Already connected to " + device + ", disconnecting " + |
| info.mRemoteDevice); |
| rejectIncomingConnection(info); |
| break; |
| } |
| } |
| } |
| }; |
| |
| private void rejectIncomingConnection(IncomingConnectionInfo info) { |
| HeadsetBase headset = new HeadsetBase(mPowerManager, mAdapter, |
| info.mRemoteDevice, info.mSocketFd, info.mRfcommChan, null); |
| headset.disconnect(); |
| } |
| |
| |
| private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() { |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| BluetoothDevice device = |
| intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| |
| BluetoothDevice currDevice = getCurrentDevice(); |
| int state = BluetoothProfile.STATE_DISCONNECTED; |
| if (currDevice != null) { |
| state = mRemoteHeadsets.get(currDevice).mState; |
| } |
| |
| if ((state == BluetoothProfile.STATE_CONNECTED || |
| state == BluetoothProfile.STATE_CONNECTING) && |
| action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) && |
| device.equals(currDevice)) { |
| try { |
| mBinder.disconnect(currDevice); |
| } catch (RemoteException e) {} |
| } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { |
| switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, |
| BluetoothAdapter.ERROR)) { |
| case BluetoothAdapter.STATE_ON: |
| mAg.start(mIncomingConnectionHandler); |
| mBtHandsfree.onBluetoothEnabled(); |
| break; |
| case BluetoothAdapter.STATE_TURNING_OFF: |
| mBtHandsfree.onBluetoothDisabled(); |
| mAg.stop(); |
| if (currDevice != null) { |
| try { |
| mBinder.disconnect(currDevice); |
| } catch (RemoteException e) {} |
| } |
| break; |
| } |
| } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { |
| int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); |
| if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) { |
| mBtHandsfree.sendScoGainUpdate(intent.getIntExtra( |
| AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0)); |
| } |
| |
| } else if (action.equals(BluetoothDevice.ACTION_UUID)) { |
| if (device.equals(mDeviceSdpQuery) && device.equals(currDevice)) { |
| // We have got SDP records for the device we are interested in. |
| getSdpRecordsAndConnect(device); |
| } |
| } |
| } |
| }; |
| |
| private static final int CONNECT_HEADSET_DELAYED = 1; |
| private Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case CONNECT_HEADSET_DELAYED: |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| getSdpRecordsAndConnect(device); |
| break; |
| } |
| } |
| }; |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return mBinder; |
| } |
| |
| // ------------------------------------------------------------------ |
| // Bluetooth Headset Connect |
| // ------------------------------------------------------------------ |
| private static final int RFCOMM_CONNECTED = 1; |
| private static final int RFCOMM_ERROR = 2; |
| |
| private long mTimestamp; |
| |
| /** |
| * Thread for RFCOMM connection |
| * Messages are sent to mConnectingStatusHandler as connection progresses. |
| */ |
| private RfcommConnectThread mConnectThread; |
| private class RfcommConnectThread extends Thread { |
| private BluetoothDevice device; |
| private int channel; |
| private int type; |
| |
| private static final int EINTERRUPT = -1000; |
| private static final int ECONNREFUSED = -111; |
| |
| public RfcommConnectThread(BluetoothDevice device, int channel, int type) { |
| super(); |
| this.device = device; |
| this.channel = channel; |
| this.type = type; |
| } |
| |
| private int waitForConnect(HeadsetBase headset) { |
| // Try to connect for 20 seconds |
| int result = 0; |
| for (int i=0; i < 40 && result == 0; i++) { |
| // waitForAsyncConnect returns 0 on timeout, 1 on success, < 0 on error. |
| result = headset.waitForAsyncConnect(500, mConnectedStatusHandler); |
| if (isInterrupted()) { |
| headset.disconnect(); |
| return EINTERRUPT; |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public void run() { |
| long timestamp; |
| |
| timestamp = System.currentTimeMillis(); |
| HeadsetBase headset = new HeadsetBase(mPowerManager, mAdapter, |
| device, channel); |
| |
| int result = waitForConnect(headset); |
| |
| if (result != EINTERRUPT && result != 1) { |
| if (result == ECONNREFUSED && mDeviceSdpQuery == null) { |
| // The rfcomm channel number might have changed, do SDP |
| // query and try to connect again. |
| mDeviceSdpQuery = getCurrentDevice(); |
| device.fetchUuidsWithSdp(); |
| mConnectThread = null; |
| return; |
| } else { |
| Log.i(TAG, "Trying to connect to rfcomm socket again after 1 sec"); |
| try { |
| sleep(1000); // 1 second |
| } catch (InterruptedException e) { |
| return; |
| } |
| } |
| result = waitForConnect(headset); |
| } |
| mDeviceSdpQuery = null; |
| if (result == EINTERRUPT) return; |
| |
| if (DBG) log("RFCOMM connection attempt took " + |
| (System.currentTimeMillis() - timestamp) + " ms"); |
| if (isInterrupted()) { |
| headset.disconnect(); |
| return; |
| } |
| if (result < 0) { |
| Log.w(TAG, "headset.waitForAsyncConnect() error: " + result); |
| mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget(); |
| return; |
| } else if (result == 0) { |
| mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget(); |
| Log.w(TAG, "mHeadset.waitForAsyncConnect() error: " + result + "(timeout)"); |
| return; |
| } else { |
| mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget(); |
| } |
| } |
| } |
| |
| /** |
| * Receives events from mConnectThread back in the main thread. |
| */ |
| private final Handler mConnectingStatusHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| BluetoothDevice device = getCurrentDevice(); |
| if (device == null || |
| mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTING) { |
| return; // stale events |
| } |
| |
| switch (msg.what) { |
| case RFCOMM_ERROR: |
| if (DBG) log("Rfcomm error"); |
| mConnectThread = null; |
| setState(device, BluetoothProfile.STATE_DISCONNECTED); |
| break; |
| case RFCOMM_CONNECTED: |
| if (DBG) log("Rfcomm connected"); |
| mConnectThread = null; |
| HeadsetBase headset = (HeadsetBase)msg.obj; |
| setState(device, BluetoothProfile.STATE_CONNECTED); |
| |
| mRemoteHeadsets.get(device).mHeadset = headset; |
| mBtHandsfree.connectHeadset(headset, mRemoteHeadsets.get(device).mHeadsetType); |
| break; |
| } |
| } |
| }; |
| |
| /** |
| * Receives events from a connected RFCOMM socket back in the main thread. |
| */ |
| private final Handler mConnectedStatusHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case HeadsetBase.RFCOMM_DISCONNECTED: |
| mBtHandsfree.resetAtState(); |
| mBtHandsfree.setVirtualCallInProgress(false); |
| BluetoothDevice device = getCurrentDevice(); |
| if (device != null) { |
| setState(device, BluetoothProfile.STATE_DISCONNECTED); |
| } |
| break; |
| } |
| } |
| }; |
| |
| private synchronized void setState(BluetoothDevice device, int state) { |
| int prevState = mRemoteHeadsets.get(device).mState; |
| if (state != prevState) { |
| if (DBG) log("Device: " + device + |
| " Headset state" + prevState + " -> " + state); |
| if (prevState == BluetoothProfile.STATE_CONNECTED) { |
| // Headset is disconnecting, stop the parser. |
| mBtHandsfree.disconnectHeadset(); |
| } |
| Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, state); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| if (state == BluetoothProfile.STATE_DISCONNECTED) { |
| mRemoteHeadsets.get(device).mHeadset = null; |
| mRemoteHeadsets.get(device).mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN; |
| } |
| |
| mRemoteHeadsets.get(device).mState = state; |
| |
| sendBroadcast(intent, BLUETOOTH_PERM); |
| if (state == BluetoothHeadset.STATE_CONNECTED) { |
| // Set the priority to AUTO_CONNECT |
| setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT); |
| adjustOtherHeadsetPriorities(device); |
| } |
| try { |
| mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEADSET, |
| state, prevState); |
| } catch (RemoteException e) { |
| Log.e(TAG, "sendConnectionStateChange: exception"); |
| } |
| } |
| } |
| |
| private void adjustOtherHeadsetPriorities(BluetoothDevice connectedDevice) { |
| for (BluetoothDevice device : mAdapter.getBondedDevices()) { |
| if (getPriority(device) >= BluetoothHeadset.PRIORITY_AUTO_CONNECT && |
| !device.equals(connectedDevice)) { |
| setPriority(device, BluetoothHeadset.PRIORITY_ON); |
| } |
| } |
| } |
| |
| private void setPriority(BluetoothDevice device, int priority) { |
| try { |
| mBinder.setPriority(device, priority); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error while setting priority for: " + device); |
| } |
| } |
| |
| private int getPriority(BluetoothDevice device) { |
| try { |
| return mBinder.getPriority(device); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error while getting priority for: " + device); |
| } |
| return BluetoothProfile.PRIORITY_UNDEFINED; |
| } |
| |
| private synchronized void getSdpRecordsAndConnect(BluetoothDevice device) { |
| if (!device.equals(getCurrentDevice())) { |
| // stale |
| return; |
| } |
| |
| // Check if incoming connection has already connected. |
| if (mRemoteHeadsets.get(device).mState == BluetoothProfile.STATE_CONNECTED) { |
| return; |
| } |
| |
| ParcelUuid[] uuids = device.getUuids(); |
| ParcelUuid[] localUuids = mAdapter.getUuids(); |
| int type = BluetoothHandsfree.TYPE_UNKNOWN; |
| if (uuids != null) { |
| if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree) && |
| BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG)) { |
| log("SDP UUID: TYPE_HANDSFREE"); |
| type = BluetoothHandsfree.TYPE_HANDSFREE; |
| mRemoteHeadsets.get(device).mHeadsetType = type; |
| int channel = device.getServiceChannel(BluetoothUuid.Handsfree); |
| mConnectThread = new RfcommConnectThread(device, channel, type); |
| if (mAdapter.isDiscovering()) { |
| mAdapter.cancelDiscovery(); |
| } |
| mConnectThread.start(); |
| if (getPriority(device) < BluetoothHeadset.PRIORITY_AUTO_CONNECT) { |
| setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT); |
| } |
| return; |
| } else if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) && |
| BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG)) { |
| log("SDP UUID: TYPE_HEADSET"); |
| type = BluetoothHandsfree.TYPE_HEADSET; |
| mRemoteHeadsets.get(device).mHeadsetType = type; |
| int channel = device.getServiceChannel(BluetoothUuid.HSP); |
| mConnectThread = new RfcommConnectThread(device, channel, type); |
| if (mAdapter.isDiscovering()) { |
| mAdapter.cancelDiscovery(); |
| } |
| mConnectThread.start(); |
| if (getPriority(device) < BluetoothHeadset.PRIORITY_AUTO_CONNECT) { |
| setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT); |
| } |
| return; |
| } |
| } |
| log("SDP UUID: TYPE_UNKNOWN"); |
| mRemoteHeadsets.get(device).mHeadsetType = type; |
| setState(device, BluetoothProfile.STATE_DISCONNECTED); |
| return; |
| } |
| |
| /** |
| * Handlers for incoming service calls |
| */ |
| private final IBluetoothHeadset.Stub mBinder = new IBluetoothHeadset.Stub() { |
| public int getConnectionState(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device); |
| if (headset == null) { |
| return BluetoothProfile.STATE_DISCONNECTED; |
| } |
| return headset.mState; |
| } |
| |
| public List<BluetoothDevice> getConnectedDevices() { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| return getDevicesMatchingConnectionStates( |
| new int[] {BluetoothProfile.STATE_CONNECTED}); |
| } |
| |
| public boolean connect(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| BluetoothDevice currDevice = getCurrentDevice(); |
| |
| if (currDevice == device || |
| getPriority(device) == BluetoothProfile.PRIORITY_OFF) { |
| return false; |
| } |
| if (currDevice != null) { |
| disconnect(currDevice); |
| } |
| try { |
| return mBluetoothService.connectHeadset(device.getAddress()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "connectHeadset"); |
| return false; |
| } |
| } |
| } |
| |
| public boolean disconnect(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device); |
| if (headset == null || |
| headset.mState == BluetoothProfile.STATE_DISCONNECTED || |
| headset.mState == BluetoothProfile.STATE_DISCONNECTING) { |
| return false; |
| } |
| try { |
| return mBluetoothService.disconnectHeadset(device.getAddress()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "disconnectHeadset"); |
| return false; |
| } |
| } |
| } |
| |
| public synchronized boolean isAudioConnected(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| if (device.equals(mAudioConnectedDevice)) return true; |
| return false; |
| } |
| |
| public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| List<BluetoothDevice> headsets = new ArrayList<BluetoothDevice>(); |
| for (BluetoothDevice device: mRemoteHeadsets.keySet()) { |
| int headsetState = getConnectionState(device); |
| for (int state : states) { |
| if (state == headsetState) { |
| headsets.add(device); |
| break; |
| } |
| } |
| } |
| return headsets; |
| } |
| |
| public boolean startVoiceRecognition(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| if (device == null || |
| mRemoteHeadsets.get(device) == null || |
| mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED) { |
| return false; |
| } |
| return mBtHandsfree.startVoiceRecognition(); |
| } |
| } |
| |
| public boolean stopVoiceRecognition(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| if (device == null || |
| mRemoteHeadsets.get(device) == null || |
| mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED) { |
| return false; |
| } |
| |
| return mBtHandsfree.stopVoiceRecognition(); |
| } |
| } |
| |
| public int getBatteryUsageHint(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| return HeadsetBase.getAtInputCount(); |
| } |
| |
| public int getPriority(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| int priority = Settings.Secure.getInt(getContentResolver(), |
| Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()), |
| BluetoothProfile.PRIORITY_UNDEFINED); |
| return priority; |
| } |
| } |
| |
| public boolean setPriority(BluetoothDevice device, int priority) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| Settings.Secure.putInt(getContentResolver(), |
| Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()), |
| priority); |
| if (DBG) log("Saved priority " + device + " = " + priority); |
| return true; |
| } |
| } |
| |
| public boolean createIncomingConnect(BluetoothDevice device) { |
| synchronized (BluetoothHeadsetService.this) { |
| HeadsetBase headset; |
| setState(device, BluetoothProfile.STATE_CONNECTING); |
| |
| IncomingConnectionInfo info = mRemoteHeadsets.get(device).mIncomingInfo; |
| headset = new HeadsetBase(mPowerManager, mAdapter, |
| device, |
| info.mSocketFd, info.mRfcommChan, |
| mConnectedStatusHandler); |
| |
| mRemoteHeadsets.get(device).mHeadset = headset; |
| |
| mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget(); |
| return true; |
| } |
| } |
| |
| public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| if (device == null || |
| mRemoteHeadsets.get(device) == null || |
| mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED || |
| getAudioState(device) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { |
| return false; |
| } |
| return mBtHandsfree.initiateScoUsingVirtualVoiceCall(); |
| } |
| } |
| |
| public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| synchronized (BluetoothHeadsetService.this) { |
| if (device == null || |
| mRemoteHeadsets.get(device) == null || |
| mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED || |
| getAudioState(device) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { |
| return false; |
| } |
| return mBtHandsfree.terminateScoUsingVirtualVoiceCall(); |
| } |
| } |
| |
| public boolean rejectIncomingConnect(BluetoothDevice device) { |
| synchronized (BluetoothHeadsetService.this) { |
| BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device); |
| if (headset != null) { |
| IncomingConnectionInfo info = headset.mIncomingInfo; |
| rejectIncomingConnection(info); |
| } else { |
| Log.e(TAG, "Error no record of remote headset"); |
| } |
| return true; |
| } |
| } |
| |
| public boolean acceptIncomingConnect(BluetoothDevice device) { |
| synchronized (BluetoothHeadsetService.this) { |
| HeadsetBase headset; |
| BluetoothRemoteHeadset cachedHeadset = mRemoteHeadsets.get(device); |
| if (cachedHeadset == null) { |
| Log.e(TAG, "Cached Headset is Null in acceptIncomingConnect"); |
| return false; |
| } |
| IncomingConnectionInfo info = cachedHeadset.mIncomingInfo; |
| headset = new HeadsetBase(mPowerManager, mAdapter, |
| device, |
| info.mSocketFd, info.mRfcommChan, |
| mConnectedStatusHandler); |
| |
| setState(device, BluetoothProfile.STATE_CONNECTED); |
| |
| cachedHeadset.mHeadset = headset; |
| mBtHandsfree.connectHeadset(headset, cachedHeadset.mHeadsetType); |
| |
| if (DBG) log("Successfully used incoming connection"); |
| return true; |
| } |
| } |
| |
| public boolean cancelConnectThread() { |
| synchronized (BluetoothHeadsetService.this) { |
| if (mConnectThread != null) { |
| // cancel the connection thread |
| mConnectThread.interrupt(); |
| try { |
| mConnectThread.join(); |
| } catch (InterruptedException e) { |
| Log.e(TAG, "Connection cancelled twice?", e); |
| } |
| mConnectThread = null; |
| } |
| return true; |
| } |
| } |
| |
| public boolean connectHeadsetInternal(BluetoothDevice device) { |
| synchronized (BluetoothHeadsetService.this) { |
| BluetoothDevice currDevice = getCurrentDevice(); |
| if (currDevice == null) { |
| BluetoothRemoteHeadset headset = new BluetoothRemoteHeadset(); |
| mRemoteHeadsets.put(device, headset); |
| |
| setState(device, BluetoothProfile.STATE_CONNECTING); |
| if (device.getUuids() == null) { |
| // We might not have got the UUID change notification from |
| // Bluez yet, if we have just paired. Try after 1.5 secs. |
| Message msg = new Message(); |
| msg.what = CONNECT_HEADSET_DELAYED; |
| msg.obj = device; |
| mHandler.sendMessageDelayed(msg, 1500); |
| } else { |
| getSdpRecordsAndConnect(device); |
| } |
| return true; |
| } else { |
| Log.w(TAG, "connectHeadset(" + device + "): failed: already in state " + |
| mRemoteHeadsets.get(currDevice).mState + |
| " with headset " + currDevice); |
| } |
| return false; |
| } |
| } |
| |
| public boolean disconnectHeadsetInternal(BluetoothDevice device) { |
| synchronized (BluetoothHeadsetService.this) { |
| BluetoothRemoteHeadset remoteHeadset = mRemoteHeadsets.get(device); |
| if (remoteHeadset == null) return false; |
| |
| if (remoteHeadset.mState == BluetoothProfile.STATE_CONNECTED) { |
| // Send a dummy battery level message to force headset |
| // out of sniff mode so that it will immediately notice |
| // the disconnection. We are currently sending it for |
| // handsfree only. |
| // TODO: Call hci_conn_enter_active_mode() from |
| // rfcomm_send_disc() in the kernel instead. |
| // See http://b/1716887 |
| setState(device, BluetoothProfile.STATE_DISCONNECTING); |
| |
| HeadsetBase headset = remoteHeadset.mHeadset; |
| if (remoteHeadset.mHeadsetType == BluetoothHandsfree.TYPE_HANDSFREE) { |
| headset.sendURC("+CIEV: 7,3"); |
| } |
| |
| if (headset != null) { |
| headset.disconnect(); |
| headset = null; |
| } |
| setState(device, BluetoothProfile.STATE_DISCONNECTED); |
| return true; |
| } else if (remoteHeadset.mState == BluetoothProfile.STATE_CONNECTING) { |
| // The state machine would have canceled the connect thread. |
| // Just set the state here. |
| setState(device, BluetoothProfile.STATE_DISCONNECTED); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| public boolean setAudioState(BluetoothDevice device, int state) { |
| // mRemoteHeadsets handles put/get concurrency by itself |
| int prevState = mRemoteHeadsets.get(device).mAudioState; |
| mRemoteHeadsets.get(device).mAudioState = state; |
| if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { |
| mAudioConnectedDevice = device; |
| } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { |
| mAudioConnectedDevice = null; |
| } |
| Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); |
| intent.putExtra(BluetoothHeadset.EXTRA_STATE, state); |
| intent.putExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, prevState); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| sendBroadcast(intent, android.Manifest.permission.BLUETOOTH); |
| if (DBG) log("AudioStateIntent: " + device + " State: " + state |
| + " PrevState: " + prevState); |
| return true; |
| } |
| |
| public int getAudioState(BluetoothDevice device) { |
| // mRemoteHeadsets handles put/get concurrency by itself |
| BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device); |
| if (headset == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; |
| |
| return headset.mAudioState; |
| } |
| }; |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| if (DBG) log("Stopping BluetoothHeadsetService"); |
| unregisterReceiver(mBluetoothReceiver); |
| mBtHandsfree.onBluetoothDisabled(); |
| mAg.stop(); |
| sHasStarted = false; |
| BluetoothDevice device = getCurrentDevice(); |
| if (device != null) { |
| setState(device, BluetoothProfile.STATE_DISCONNECTED); |
| } |
| } |
| |
| |
| |
| private static void log(String msg) { |
| Log.d(TAG, msg); |
| } |
| } |