blob: ab298e08583f3a8c00b0dd9ab48e03784b1ffa7a [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.phone;
import android.app.Service;
import android.bluetooth.BluetoothAudioGateway;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothError;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothIntent;
import android.bluetooth.HeadsetBase;
import android.bluetooth.IBluetoothDeviceCallback;
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.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.PhoneFactory;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;
/**
* 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 mBluetooth;
private PowerManager mPowerManager;
private BluetoothAudioGateway mAg;
private HeadsetBase mHeadset;
private int mState;
private int mHeadsetType;
private BluetoothHandsfree mBtHandsfree;
private String mHeadsetAddress;
private LinkedList<String> 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();
mBluetooth = (BluetoothDevice)getSystemService(Context.BLUETOOTH_SERVICE);
mPowerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
mBtHandsfree = PhoneApp.getInstance().getBluetoothHandsfree();
mAg = new BluetoothAudioGateway(mBluetooth);
mPhone = PhoneFactory.getDefaultPhone();
mRingingCall = mPhone.getRingingCall();
mForegroundCall = mPhone.getForegroundCall();
if (mBluetooth.isEnabled()) {
mHeadsetPriority.load();
}
IntentFilter filter = new IntentFilter(
BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
filter.addAction(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION);
filter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
registerReceiver(mBluetoothIntentReceiver, filter);
mPhone.registerForPhoneStateChanged(mStateChangeHandler, PHONE_STATE_CHANGED, null);
}
@Override
public void onStart(Intent intent, int startId) {
if (mBluetooth == null) {
Log.w(TAG, "Stopping BluetoothHeadsetService: device does not have BT");
stopSelf();
} else {
if (!sHasStarted) {
if (DBG) log("Starting BluetoothHeadsetService");
if (mBluetooth.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.mAddress + " on channel " + info.mRfcommChan);
int priority = BluetoothHeadset.PRIORITY_OFF;
try {
priority = mBinder.getPriority(info.mAddress);
} catch (RemoteException e) {}
if (priority <= BluetoothHeadset.PRIORITY_OFF) {
Log.i(TAG, "Rejecting incoming connection because priority = " + priority);
// TODO: disconnect RFCOMM not ACL. Happens elsewhere too.
mBluetooth.disconnectRemoteDeviceAcl(info.mAddress);
}
switch (mState) {
case BluetoothHeadset.STATE_DISCONNECTED:
// headset connecting us, lets join
setState(BluetoothHeadset.STATE_CONNECTING);
mHeadsetAddress = info.mAddress;
HeadsetBase headset = new HeadsetBase(mPowerManager, mBluetooth, mHeadsetAddress,
info.mSocketFd, info.mRfcommChan, mConnectedStatusHandler);
mHeadsetType = type;
mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
break;
case BluetoothHeadset.STATE_CONNECTING:
if (!info.mAddress.equals(mHeadsetAddress)) {
// different headset, ignoring
Log.i(TAG, "Already attempting connect to " + mHeadsetAddress +
", disconnecting " + info.mAddress);
mBluetooth.disconnectRemoteDeviceAcl(info.mAddress);
break;
}
// 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.mAddress +
". Cancel outgoing connection.");
if (mConnectThread != null) {
mConnectThread.interrupt();
}
// Now continue with new connection, including calling callback
mHeadset = new HeadsetBase(mPowerManager, mBluetooth, mHeadsetAddress,
info.mSocketFd, info.mRfcommChan, mConnectedStatusHandler);
mHeadsetType = type;
setState(BluetoothHeadset.STATE_CONNECTED, BluetoothHeadset.RESULT_SUCCESS);
mBtHandsfree.connectHeadset(mHeadset, mHeadsetType);
// Make sure that old outgoing connect thread is dead.
if (mConnectThread != null) {
try {
// TODO: Don't block in the main thread
Log.w(TAG, "Block in main thread to join stale outgoing connection thread");
mConnectThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Connection cancelled twice eh?", e);
}
mConnectThread = null;
}
if (DBG) log("Successfully used incoming connection, and cancelled outgoing " +
" connection");
break;
case BluetoothHeadset.STATE_CONNECTED:
Log.i(TAG, "Already connected to " + mHeadsetAddress + ", disconnecting " +
info.mAddress);
mBluetooth.disconnectRemoteDeviceAcl(info.mAddress);
break;
}
}
};
private final Handler mStateChangeHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case PHONE_STATE_CHANGED:
switch(mForegroundCall.getState()) {
case DIALING:
case ALERTING:
synchronized(this) {
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
autoConnectHeadset();
}
}
}
switch(mRingingCall.getState()) {
case INCOMING:
case WAITING:
synchronized(this) {
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
autoConnectHeadset();
}
}
break;
}
}
}
};
private synchronized void autoConnectHeadset() {
if (DBG && debugDontReconnect()) {
return;
}
if (mBluetooth.isEnabled()) {
try {
mBinder.connectHeadset(null);
} catch (RemoteException e) {}
}
}
private final BroadcastReceiver mBluetoothIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
if ((mState == BluetoothHeadset.STATE_CONNECTED ||
mState == BluetoothHeadset.STATE_CONNECTING) &&
action.equals(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION) &&
address.equals(mHeadsetAddress)) {
try {
mBinder.disconnectHeadset();
} catch (RemoteException e) {}
} else if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) {
switch (intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE,
BluetoothError.ERROR)) {
case BluetoothDevice.BLUETOOTH_STATE_ON:
mHeadsetPriority.load();
mHandler.sendMessageDelayed(mHandler.obtainMessage(RECONNECT_LAST_HEADSET), 8000);
mAg.start(mIncomingConnectionHandler);
mBtHandsfree.onBluetoothEnabled();
break;
case BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF:
mBtHandsfree.onBluetoothDisabled();
mAg.stop();
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE);
break;
}
} else if (action.equals(BluetoothIntent.BOND_STATE_CHANGED_ACTION)) {
int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE,
BluetoothError.ERROR);
switch(bondState) {
case BluetoothDevice.BOND_BONDED:
mHeadsetPriority.set(address, BluetoothHeadset.PRIORITY_AUTO);
break;
case BluetoothDevice.BOND_NOT_BONDED:
mHeadsetPriority.set(address, BluetoothHeadset.PRIORITY_OFF);
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));
}
}
}
};
private static final int RECONNECT_LAST_HEADSET = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case RECONNECT_LAST_HEADSET:
autoConnectHeadset();
break;
}
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// ------------------------------------------------------------------
// Bluetooth Headset Connect
// ------------------------------------------------------------------
private static final int SDP_RESULT = 1;
private static final int RFCOMM_CONNECTED = 2;
private static final int RFCOMM_ERROR = 3;
private static final short HEADSET_UUID16 = 0x1108;
private static final short HANDSFREE_UUID16 = 0x111E;
private long mTimestamp;
IBluetoothDeviceCallback.Stub mDeviceCallback = new IBluetoothDeviceCallback.Stub() {
public void onGetRemoteServiceChannelResult(String address, int channel) {
mConnectingStatusHandler.obtainMessage(SDP_RESULT, channel, -1, address)
.sendToTarget();
}
public void onCreateBondingResult(String address, int result) { }
public void onEnableResult(int result) {}
};
/**
* Thread for RFCOMM connection
* Messages are sent to mConnectingStatusHandler as connection progresses.
*/
private RfcommConnectThread mConnectThread;
private class RfcommConnectThread extends Thread {
private String address;
private int channel;
private int type;
public RfcommConnectThread(String address, int channel, int type) {
super();
this.address = address;
this.channel = channel;
this.type = type;
}
@Override
public void run() {
long timestamp;
timestamp = System.currentTimeMillis();
HeadsetBase headset = new HeadsetBase(mPowerManager, mBluetooth, address, channel);
// Try to connect for 20 seconds
int result = 0;
for (int i=0; i < 40 && result == 0; i++) {
result = headset.waitForAsyncConnect(500, mConnectedStatusHandler);
if (isInterrupted()) {
headset.disconnect();
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) {
Log.w(TAG, "mHeadset.waitForAsyncConnect() error: " + result + "(timeout)");
mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
return;
} else {
mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
}
}
};
private void doHeadsetSdp() {
if (DBG) log("Headset SDP request");
mTimestamp = System.currentTimeMillis();
mHeadsetType = BluetoothHandsfree.TYPE_HEADSET;
if (!mBluetooth.getRemoteServiceChannel(mHeadsetAddress, HEADSET_UUID16,
mDeviceCallback)) {
Log.e(TAG, "Could not start headset SDP query");
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE);
}
}
private void doHandsfreeSdp() {
if (DBG) log("Handsfree SDP request");
mTimestamp = System.currentTimeMillis();
mHeadsetType = BluetoothHandsfree.TYPE_HANDSFREE;
if (!mBluetooth.getRemoteServiceChannel(mHeadsetAddress, HANDSFREE_UUID16,
mDeviceCallback)) {
Log.e(TAG, "Could not start handsfree SDP query");
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE);
}
}
/**
* 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 SDP_RESULT:
if (DBG) log("SDP request returned " + msg.arg1 + " (" +
(System.currentTimeMillis() - mTimestamp + " ms)"));
if (!((String)msg.obj).equals(mHeadsetAddress)) {
return; // stale SDP result
}
if (msg.arg1 > 0) {
mConnectThread = new RfcommConnectThread(mHeadsetAddress, msg.arg1,
mHeadsetType);
mConnectThread.start();
} else {
if (msg.arg1 == -1 && mHeadsetType == BluetoothHandsfree.TYPE_HANDSFREE) {
doHeadsetSdp(); // try headset
} else {
setState(BluetoothHeadset.STATE_DISCONNECTED,
BluetoothHeadset.RESULT_FAILURE);
}
}
break;
case RFCOMM_ERROR:
if (DBG) log("Rfcomm error");
mConnectThread = null;
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_FAILURE);
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);
break;
}
}
};
private void setState(int state) {
setState(state, BluetoothHeadset.RESULT_SUCCESS);
}
private synchronized void setState(int state, int result) {
if (state != mState) {
if (DBG) log("Headset state " + mState + " -> " + state + ", result = " + result);
if (mState == BluetoothHeadset.STATE_CONNECTED) {
// no longer connected - make sure BT audio is taken down
mBtHandsfree.audioOff();
mBtHandsfree.disconnectHeadset();
}
Intent intent = new Intent(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
intent.putExtra(BluetoothIntent.HEADSET_PREVIOUS_STATE, mState);
mState = state;
intent.putExtra(BluetoothIntent.HEADSET_STATE, mState);
intent.putExtra(BluetoothIntent.ADDRESS, mHeadsetAddress);
sendBroadcast(intent, BLUETOOTH_PERM);
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
mHeadset = null;
mHeadsetAddress = null;
mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
if (mAutoConnectQueue != null) {
doNextAutoConnect();
}
} else if (mState == BluetoothHeadset.STATE_CONNECTED) {
mAutoConnectQueue = null; // cancel further auto-connection
mHeadsetPriority.bump(mHeadsetAddress.toUpperCase());
}
}
}
private synchronized boolean doNextAutoConnect() {
if (mAutoConnectQueue == null || mAutoConnectQueue.size() == 0) {
mAutoConnectQueue = null;
return false;
}
mHeadsetAddress = mAutoConnectQueue.removeFirst();
if (DBG) log("pulled " + mHeadsetAddress + " off auto-connect queue");
setState(BluetoothHeadset.STATE_CONNECTING);
doHandsfreeSdp();
return true;
}
/**
* 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 String getHeadsetAddress() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
return null;
}
return mHeadsetAddress;
}
public boolean connectHeadset(String address) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address) && address != null) {
return false;
}
synchronized (BluetoothHeadsetService.this) {
if (mState == BluetoothHeadset.STATE_CONNECTED ||
mState == BluetoothHeadset.STATE_CONNECTING) {
Log.w(TAG, "connectHeadset(" + address + "): failed: already in state " +
mState + " with headset " + mHeadsetAddress);
return false;
}
if (address == null) {
mAutoConnectQueue = mHeadsetPriority.getSorted();
return doNextAutoConnect();
}
mHeadsetAddress = address;
setState(BluetoothHeadset.STATE_CONNECTING);
doHandsfreeSdp();
}
return true;
}
public boolean isConnected(String address) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mState == BluetoothHeadset.STATE_CONNECTED && mHeadsetAddress.equals(address);
}
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);
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);
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(String address, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address) ||
priority < BluetoothHeadset.PRIORITY_OFF) {
return false;
}
mHeadsetPriority.set(address.toUpperCase(), priority);
return true;
}
public int getPriority(String address) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return -1; //TODO: BluetoothError.
}
return mHeadsetPriority.get(address.toUpperCase());
}
public int getBatteryUsageHint() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return HeadsetBase.getAtInputCount();
}
};
@Override
public void onDestroy() {
super.onDestroy();
if (DBG) log("Stopping BluetoothHeadsetService");
unregisterReceiver(mBluetoothIntentReceiver);
mBtHandsfree.onBluetoothDisabled();
mAg.stop();
sHasStarted = false;
setState(BluetoothHeadset.STATE_DISCONNECTED, BluetoothHeadset.RESULT_CANCELED);
mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
}
/** operates on UPPER CASE addresses */
private class HeadsetPriority {
private HashMap<String, Integer> mPriority = new HashMap<String, Integer>();
public synchronized boolean load() {
String[] addresses = mBluetooth.listBonds();
if (addresses == null) {
return false; // for example, bluetooth is off
}
for (String address : addresses) {
load(address);
}
return true;
}
private synchronized int load(String address) {
int priority = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.getBluetoothHeadsetPriorityKey(address),
BluetoothHeadset.PRIORITY_OFF);
mPriority.put(address, new Integer(priority));
if (DBG) log("Loaded priority " + address + " = " + priority);
return priority;
}
public synchronized int get(String address) {
Integer priority = mPriority.get(address);
if (priority == null) {
return load(address);
}
return priority.intValue();
}
public synchronized void set(String address, int priority) {
int oldPriority = get(address);
if (oldPriority == priority) {
return;
}
mPriority.put(address, new Integer(priority));
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.getBluetoothHeadsetPriorityKey(address),
priority);
if (DBG) log("Saved priority " + address + " = " + priority);
}
/** Mark this headset as highest priority */
public synchronized void bump(String address) {
int oldPriority = get(address);
int maxPriority = BluetoothHeadset.PRIORITY_OFF;
// Find max, not including given address
for (String a : mPriority.keySet()) {
if (address.equals(a)) continue;
int p = mPriority.get(a).intValue();
if (p > maxPriority) {
maxPriority = p;
}
}
if (maxPriority >= oldPriority) {
int p = maxPriority + 1;
set(address, p);
if (p >= Integer.MAX_VALUE) {
rebalance();
}
}
}
/** shifts all non-zero priorities to be monotonically increasing from
* PRIORITY_AUTO */
private synchronized void rebalance() {
LinkedList<String> sorted = getSorted();
if (DBG) log("Rebalancing " + sorted.size() + " headset priorities");
ListIterator<String> li = sorted.listIterator(sorted.size());
int priority = BluetoothHeadset.PRIORITY_AUTO;
while (li.hasPrevious()) {
String address = li.previous();
set(address, priority);
priority++;
}
}
/** Get list of headsets sorted by decreasing priority.
* Headsets with priority equal to PRIORITY_OFF are not included */
public synchronized LinkedList<String> getSorted() {
LinkedList<String> sorted = new LinkedList<String>();
HashMap<String, Integer> toSort = new HashMap<String, Integer>(mPriority);
// add in sorted order. this could be more efficient.
while (true) {
String maxAddress = null;
int maxPriority = BluetoothHeadset.PRIORITY_OFF;
for (String address : toSort.keySet()) {
int priority = toSort.get(address).intValue();
if (priority > maxPriority) {
maxAddress = address;
maxPriority = priority;
}
}
if (maxAddress == null) {
break;
}
sorted.addLast(maxAddress);
toSort.remove(maxAddress);
}
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);
}
}