blob: fb00792ae2a8019ea154383ecb95376f396dc2d2 [file] [log] [blame]
/*
* 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.phone2;
import android.app.Service;
import android.bluetooth.BluetoothAudioGateway;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.HeadsetBase;
import android.bluetooth.IBluetoothHeadset;
import android.os.ParcelUuid;
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.PowerManager;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.sip.SipPhoneFactory;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Set;
/**
* Provides Bluetooth Headset and Handsfree profile, as a service in
* the Phone application.
* @hide
*/
public class BluetoothHeadsetService extends Service {
private static final String TAG = "BT 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 PowerManager mPowerManager;
private BluetoothAudioGateway mAg;
private HeadsetBase mHeadset;
private int mState;
private int mHeadsetType;
private BluetoothHandsfree mBtHandsfree;
private BluetoothDevice mRemoteDevice;
private LinkedList<BluetoothDevice> mAutoConnectQueue;
private Call mForegroundCall;
private Call mRingingCall;
private Phone mPhone;
private final HeadsetPriority mHeadsetPriority = new HeadsetPriority();
public BluetoothHeadsetService() {
mState = BluetoothHeadset.STATE_DISCONNECTED;
mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
}
@Override
public void onCreate() {
super.onCreate();
mAdapter = BluetoothAdapter.getDefaultAdapter();
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mBtHandsfree = PhoneApp.getInstance().getBluetoothHandsfree();
mAg = new BluetoothAudioGateway(mAdapter);
mPhone = SipPhoneFactory.getDefaultPhone();
mRingingCall = mPhone.getRingingCall();
mForegroundCall = mPhone.getForegroundCall();
if (mAdapter.isEnabled()) {
mHeadsetPriority.load();
}
IntentFilter filter = new IntentFilter(
BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(BluetoothDevice.ACTION_UUID);
registerReceiver(mBluetoothReceiver, filter);
}
@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();
// BT might have only just started, wait 6 seconds until
// SDP records are registered before reconnecting headset
mHandler.sendMessageDelayed(mHandler.obtainMessage(RECONNECT_LAST_HEADSET),
6000);
}
sHasStarted = true;
}
}
}
private final Handler mIncomingConnectionHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
BluetoothAudioGateway.IncomingConnectionInfo info =
(BluetoothAudioGateway.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 = BluetoothHeadset.PRIORITY_OFF;
HeadsetBase headset;
try {
priority = mBinder.getPriority(info.mRemoteDevice);
} catch (RemoteException e) {}
if (priority <= BluetoothHeadset.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();
return;
}
switch (mState) {
case BluetoothHeadset.STATE_DISCONNECTED:
// headset connecting us, lets join
mRemoteDevice = info.mRemoteDevice;
setState(BluetoothHeadset.STATE_CONNECTING);
headset = new HeadsetBase(mPowerManager, mAdapter, mRemoteDevice, info.mSocketFd,
info.mRfcommChan, mConnectedStatusHandler);
mHeadsetType = type;
mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
break;
case BluetoothHeadset.STATE_CONNECTING:
if (!info.mRemoteDevice.equals(mRemoteDevice)) {
// different headset, ignoring
Log.i(TAG, "Already attempting connect to " + mRemoteDevice +
", disconnecting " + info.mRemoteDevice);
headset = new HeadsetBase(mPowerManager, mAdapter, info.mRemoteDevice,
info.mSocketFd, info.mRfcommChan, null);
headset.disconnect();
}
// If we are here, we are in danger of a race condition
// incoming rfcomm connection, but we are also attempting an
// outgoing connection. Lets try and interrupt the outgoing
// connection.
Log.i(TAG, "Incoming and outgoing connections to " + info.mRemoteDevice +
". Cancel outgoing connection.");
if (mConnectThread != null) {
mConnectThread.interrupt();
mConnectThread = null;
}
// Now continue with new connection, including calling callback
mHeadset = new HeadsetBase(mPowerManager, mAdapter, mRemoteDevice,
info.mSocketFd, info.mRfcommChan, mConnectedStatusHandler);
mHeadsetType = type;
setState(BluetoothHeadset.STATE_CONNECTED, BluetoothHeadset.RESULT_SUCCESS);
mBtHandsfree.connectHeadset(mHeadset, mHeadsetType);
if (DBG) log("Successfully used incoming connection");
break;
case BluetoothHeadset.STATE_CONNECTED:
Log.i(TAG, "Already connected to " + mRemoteDevice + ", disconnecting " +
info.mRemoteDevice);
headset = new HeadsetBase(mPowerManager, mAdapter, info.mRemoteDevice,
info.mSocketFd, info.mRfcommChan, null);
headset.disconnect();
break;
}
}
};
private synchronized void autoConnectHeadset() {
if (DBG && debugDontReconnect()) {
return;
}
if (mAdapter.isEnabled()) {
try {
mBinder.connectHeadset(null);
} catch (RemoteException e) {}
}
}
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);
if ((mState == BluetoothHeadset.STATE_CONNECTED ||
mState == BluetoothHeadset.STATE_CONNECTING) &&
action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) &&
device.equals(mRemoteDevice)) {
try {
mBinder.disconnectHeadset();
} catch (RemoteException e) {}
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR)) {
case BluetoothAdapter.STATE_ON:
mHeadsetPriority.load();
mHandler.sendMessageDelayed(mHandler.obtainMessage(RECONNECT_LAST_HEADSET), 8000);
mAg.start(mIncomingConnectionHandler);
mBtHandsfree.onBluetoothEnabled();
break;
case BluetoothAdapter.STATE_TURNING_OFF:
mBtHandsfree.onBluetoothDisabled();
mAg.stop();
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE,
BluetoothHeadset.LOCAL_DISCONNECT);
break;
}
} else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR);
switch(bondState) {
case BluetoothDevice.BOND_BONDED:
if (mHeadsetPriority.get(device) == BluetoothHeadset.PRIORITY_UNDEFINED) {
mHeadsetPriority.set(device, BluetoothHeadset.PRIORITY_ON);
}
break;
case BluetoothDevice.BOND_NONE:
mHeadsetPriority.set(device, BluetoothHeadset.PRIORITY_UNDEFINED);
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)) {
// We have got SDP records for the device we are interested in.
getSdpRecordsAndConnect();
}
}
}
};
private static final int RECONNECT_LAST_HEADSET = 1;
private static final int CONNECT_HEADSET_DELAYED = 2;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case RECONNECT_LAST_HEADSET:
autoConnectHeadset();
break;
case CONNECT_HEADSET_DELAYED:
getSdpRecordsAndConnect();
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 = mRemoteDevice;
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) {}
}
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) {
if (mState != BluetoothHeadset.STATE_CONNECTING) {
return; // stale events
}
switch (msg.what) {
case RFCOMM_ERROR:
if (DBG) log("Rfcomm error");
mConnectThread = null;
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE,
BluetoothHeadset.LOCAL_DISCONNECT);
break;
case RFCOMM_CONNECTED:
if (DBG) log("Rfcomm connected");
mConnectThread = null;
mHeadset = (HeadsetBase)msg.obj;
setState(BluetoothHeadset.STATE_CONNECTED, BluetoothHeadset.RESULT_SUCCESS);
mBtHandsfree.connectHeadset(mHeadset, 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:
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE,
BluetoothHeadset.REMOTE_DISCONNECT);
break;
}
}
};
private void setState(int state) {
setState(state, BluetoothHeadset.RESULT_SUCCESS);
}
private void setState(int state, int result) {
setState(state, result, -1);
}
private synchronized void setState(int state, int result, int initiator) {
if (state != mState) {
if (DBG) log("Headset state " + mState + " -> " + state + ", result = " + result);
if (mState == BluetoothHeadset.STATE_CONNECTED) {
mBtHandsfree.disconnectHeadset();
}
Intent intent = new Intent(BluetoothHeadset.ACTION_STATE_CHANGED);
intent.putExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, mState);
mState = state;
intent.putExtra(BluetoothHeadset.EXTRA_STATE, mState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
// Add Extra EXTRA_DISCONNECT_INITIATOR for DISCONNECTED state
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
if (initiator == -1) {
log("Headset Disconnected Intent without Disconnect Initiator extra");
} else {
intent.putExtra(BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
initiator);
}
}
sendBroadcast(intent, BLUETOOTH_PERM);
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
mHeadset = null;
mRemoteDevice = null;
mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
if (mAutoConnectQueue != null) {
doNextAutoConnect();
}
} else if (mState == BluetoothHeadset.STATE_CONNECTING) {
// Set the priority to AUTO_CONNECT
mHeadsetPriority.set(mRemoteDevice, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
} else if (mState == BluetoothHeadset.STATE_CONNECTED) {
mAutoConnectQueue = null; // cancel further auto-connection
mHeadsetPriority.bump(mRemoteDevice);
}
}
}
private void getSdpRecordsAndConnect() {
ParcelUuid[] uuids = mRemoteDevice.getUuids();
if (uuids != null) {
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) {
log("SDP UUID: TYPE_HANDSFREE");
mHeadsetType = BluetoothHandsfree.TYPE_HANDSFREE;
int channel = mRemoteDevice.getServiceChannel(BluetoothUuid.Handsfree);
mConnectThread = new RfcommConnectThread(mRemoteDevice, channel, mHeadsetType);
mConnectThread.start();
return;
} else if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) {
log("SDP UUID: TYPE_HEADSET");
mHeadsetType = BluetoothHandsfree.TYPE_HEADSET;
int channel = mRemoteDevice.getServiceChannel(BluetoothUuid.HSP);
mConnectThread = new RfcommConnectThread(mRemoteDevice, channel, mHeadsetType);
mConnectThread.start();
return;
}
}
log("SDP UUID: TYPE_UNKNOWN");
mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
setState(BluetoothHeadset.STATE_DISCONNECTED,
BluetoothHeadset.RESULT_FAILURE, BluetoothHeadset.LOCAL_DISCONNECT);
return;
}
private synchronized boolean doNextAutoConnect() {
if (mAutoConnectQueue == null || mAutoConnectQueue.size() == 0) {
mAutoConnectQueue = null;
return false;
}
mRemoteDevice = mAutoConnectQueue.removeFirst();
// Don't auto connect with docks if we are docked with the dock.
if (isPhoneDocked(mRemoteDevice)) return doNextAutoConnect();
if (DBG) log("pulled " + mRemoteDevice + " off auto-connect queue");
setState(BluetoothHeadset.STATE_CONNECTING);
getSdpRecordsAndConnect();
return true;
}
private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
// This works only because these broadcast intents are "sticky"
Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
if (i != null) {
int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null && autoConnectDevice.equals(device)) {
return true;
}
}
}
return false;
}
/**
* Handlers for incoming service calls
*/
private final IBluetoothHeadset.Stub mBinder = new IBluetoothHeadset.Stub() {
public int getState() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mState;
}
public BluetoothDevice getCurrentHeadset() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
return null;
}
return mRemoteDevice;
}
public boolean connectHeadset(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
synchronized (BluetoothHeadsetService.this) {
if (mState == BluetoothHeadset.STATE_CONNECTED ||
mState == BluetoothHeadset.STATE_CONNECTING) {
Log.w(TAG, "connectHeadset(" + device + "): failed: already in state " +
mState + " with headset " + mRemoteDevice);
return false;
}
if (device == null) {
mAutoConnectQueue = mHeadsetPriority.getSorted();
return doNextAutoConnect();
}
mRemoteDevice = device;
setState(BluetoothHeadset.STATE_CONNECTING);
if (mRemoteDevice.getUuids() == null) {
// We might not have got the UUID change notification from
// Bluez yet, if we have just paired. Try after 1.5 secs.
mHandler.sendMessageDelayed(
mHandler.obtainMessage(CONNECT_HEADSET_DELAYED), 1500);
} else {
getSdpRecordsAndConnect();
}
}
return true;
}
public boolean isConnected(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mState == BluetoothHeadset.STATE_CONNECTED && mRemoteDevice.equals(device);
}
public void disconnectHeadset() {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
synchronized (BluetoothHeadsetService.this) {
switch (mState) {
case BluetoothHeadset.STATE_CONNECTING:
if (mConnectThread != null) {
// cancel the connection thread
mConnectThread.interrupt();
try {
mConnectThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Connection cancelled twice?", e);
}
mConnectThread = null;
}
if (mHeadset != null) {
mHeadset.disconnect();
mHeadset = null;
}
setState(BluetoothHeadset.STATE_DISCONNECTED,
BluetoothHeadset.RESULT_CANCELED,
BluetoothHeadset.LOCAL_DISCONNECT);
break;
case BluetoothHeadset.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
if (mHeadsetType == BluetoothHandsfree.TYPE_HANDSFREE) {
mHeadset.sendURC("+CIEV: 7,3");
}
if (mHeadset != null) {
mHeadset.disconnect();
mHeadset = null;
}
setState(BluetoothHeadset.STATE_DISCONNECTED,
BluetoothHeadset.RESULT_CANCELED,
BluetoothHeadset.LOCAL_DISCONNECT);
break;
}
}
}
public boolean startVoiceRecognition() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
synchronized (BluetoothHeadsetService.this) {
if (mState != BluetoothHeadset.STATE_CONNECTED) {
return false;
}
return mBtHandsfree.startVoiceRecognition();
}
}
public boolean stopVoiceRecognition() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
synchronized (BluetoothHeadsetService.this) {
if (mState != BluetoothHeadset.STATE_CONNECTED) {
return false;
}
return mBtHandsfree.stopVoiceRecognition();
}
}
public boolean setPriority(BluetoothDevice device, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (priority < BluetoothHeadset.PRIORITY_OFF) {
return false;
}
mHeadsetPriority.set(device, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mHeadsetPriority.get(device);
}
public int getBatteryUsageHint() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return HeadsetBase.getAtInputCount();
}
};
@Override
public void onDestroy() {
super.onDestroy();
if (DBG) log("Stopping BluetoothHeadsetService");
unregisterReceiver(mBluetoothReceiver);
mBtHandsfree.onBluetoothDisabled();
mAg.stop();
sHasStarted = false;
setState(BluetoothHeadset.STATE_DISCONNECTED,
BluetoothHeadset.RESULT_CANCELED,
BluetoothHeadset.LOCAL_DISCONNECT);
mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
}
private class HeadsetPriority {
private HashMap<BluetoothDevice, Integer> mPriority =
new HashMap<BluetoothDevice, Integer>();
public synchronized boolean load() {
Set<BluetoothDevice> devices = mAdapter.getBondedDevices();
if (devices == null) {
return false; // for example, bluetooth is off
}
for (BluetoothDevice device : devices) {
load(device);
}
return true;
}
private synchronized int load(BluetoothDevice device) {
int priority = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()),
BluetoothHeadset.PRIORITY_UNDEFINED);
mPriority.put(device, new Integer(priority));
if (DBG) log("Loaded priority " + device + " = " + priority);
return priority;
}
public synchronized int get(BluetoothDevice device) {
Integer priority = mPriority.get(device);
if (priority == null) {
return load(device);
}
return priority.intValue();
}
public synchronized void set(BluetoothDevice device, int priority) {
int oldPriority = get(device);
if (oldPriority == priority) {
return;
}
mPriority.put(device, new Integer(priority));
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()),
priority);
if (DBG) log("Saved priority " + device + " = " + priority);
}
/** Mark this headset as highest priority */
public synchronized void bump(BluetoothDevice device) {
int oldPriority = get(device);
int maxPriority = BluetoothHeadset.PRIORITY_AUTO_CONNECT;
// Find max, not including given address
for (BluetoothDevice d : mPriority.keySet()) {
if (device.equals(d)) continue;
int p = mPriority.get(d).intValue();
if (p > maxPriority) {
maxPriority = p;
}
}
if (maxPriority >= oldPriority) {
int p = maxPriority + 1;
set(device, p);
if (p >= Integer.MAX_VALUE) {
rebalance();
}
}
}
/** shifts all non-zero priorities to be monotonically increasing from
* PRIORITY_AUTO_CONNECT */
private synchronized void rebalance() {
LinkedList<BluetoothDevice> sorted = getSorted();
if (DBG) log("Rebalancing " + sorted.size() + " headset priorities");
ListIterator<BluetoothDevice> li = sorted.listIterator(sorted.size());
int priority = BluetoothHeadset.PRIORITY_AUTO_CONNECT;
while (li.hasPrevious()) {
BluetoothDevice device = li.previous();
set(device, priority);
priority++;
}
}
/** Get list of headsets sorted by decreasing priority.
* Headsets with priority less than AUTO_CONNECT are not included */
public synchronized LinkedList<BluetoothDevice> getSorted() {
LinkedList<BluetoothDevice> sorted = new LinkedList<BluetoothDevice>();
HashMap<BluetoothDevice, Integer> toSort =
new HashMap<BluetoothDevice, Integer>(mPriority);
// add in sorted order. this could be more efficient.
while (true) {
BluetoothDevice maxDevice = null;
int maxPriority = BluetoothHeadset.PRIORITY_AUTO_CONNECT;
for (BluetoothDevice device : toSort.keySet()) {
int priority = toSort.get(device).intValue();
if (priority >= maxPriority) {
maxDevice = device;
maxPriority = priority;
}
}
if (maxDevice == null) {
break;
}
sorted.addLast(maxDevice);
toSort.remove(maxDevice);
}
return sorted;
}
}
/** If this property is false, then don't auto-reconnect BT headset */
private static final String DEBUG_AUTO_RECONNECT = "debug.bt.hshfp.auto_reconnect";
private boolean debugDontReconnect() {
return (!SystemProperties.getBoolean(DEBUG_AUTO_RECONNECT, true));
}
private static void log(String msg) {
Log.d(TAG, msg);
}
}