blob: 0b6478b7273479b236131074c7cc61d13b3a33fe [file] [log] [blame]
/*
* Copyright (C) 2012 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.bluetooth.btservice;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothDevice;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.hid.HidService;
import com.android.bluetooth.hfp.HeadsetService;
import android.content.Context;
import android.content.Intent;
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.util.ArrayList;
/**
* This state machine handles Bluetooth Adapter State.
* States:
* {@link StableState} : No device is in bonding / unbonding state.
* {@link PendingCommandState} : Some device is in bonding / unbonding state.
* TODO(BT) This class can be removed and this logic moved to the stack.
*/
final class BondStateMachine extends StateMachine {
private static final boolean DBG = false;
private static final String TAG = "BluetoothBondStateMachine";
static final int CREATE_BOND = 1;
static final int CANCEL_BOND = 2;
static final int REMOVE_BOND = 3;
static final int BONDING_STATE_CHANGE = 4;
static final int BOND_STATE_NONE = 0;
static final int BOND_STATE_BONDING = 1;
static final int BOND_STATE_BONDED = 2;
private AdapterService mAdapterService;
private AdapterProperties mAdapterProperties;
private RemoteDevices mRemoteDevices;
private BluetoothAdapter mAdapter;
private PendingCommandState mPendingCommandState = new PendingCommandState();
private StableState mStableState = new StableState();
private BondStateMachine(AdapterService service,
AdapterProperties prop, RemoteDevices remoteDevices) {
super("BondStateMachine:");
addState(mStableState);
addState(mPendingCommandState);
mRemoteDevices = remoteDevices;
mAdapterService = service;
mAdapterProperties = prop;
mAdapter = BluetoothAdapter.getDefaultAdapter();
setInitialState(mStableState);
}
public static BondStateMachine make(AdapterService service,
AdapterProperties prop, RemoteDevices remoteDevices) {
Log.d(TAG, "make");
BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
bsm.start();
return bsm;
}
public void doQuit() {
quitNow();
}
public void cleanup() {
mAdapterService = null;
mRemoteDevices = null;
mAdapterProperties = null;
}
private class StableState extends State {
@Override
public void enter() {
infoLog("StableState(): Entering Off State");
}
@Override
public boolean processMessage(Message msg) {
BluetoothDevice dev = (BluetoothDevice)msg.obj;
switch(msg.what) {
case CREATE_BOND:
createBond(dev, true);
break;
case REMOVE_BOND:
removeBond(dev, true);
break;
case BONDING_STATE_CHANGE:
int newState = msg.arg1;
/* if incoming pairing, transition to pending state */
if (newState == BluetoothDevice.BOND_BONDING)
{
sendIntent(dev, newState, 0);
transitionTo(mPendingCommandState);
}
else
{
Log.e(TAG, "In stable state, received invalid newState: " + newState);
}
break;
case CANCEL_BOND:
default:
Log.e(TAG, "Received unhandled state: " + msg.what);
return false;
}
return true;
}
}
private class PendingCommandState extends State {
private final ArrayList<BluetoothDevice> mDevices =
new ArrayList<BluetoothDevice>();
@Override
public void enter() {
infoLog("Entering PendingCommandState State");
BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
}
@Override
public boolean processMessage(Message msg) {
BluetoothDevice dev = (BluetoothDevice)msg.obj;
boolean result = false;
if (mDevices.contains(dev) &&
msg.what != CANCEL_BOND && msg.what != BONDING_STATE_CHANGE) {
deferMessage(msg);
return true;
}
switch (msg.what) {
case CREATE_BOND:
result = createBond(dev, false);
break;
case REMOVE_BOND:
result = removeBond(dev, false);
break;
case CANCEL_BOND:
result = cancelBond(dev);
break;
case BONDING_STATE_CHANGE:
int newState = msg.arg1;
int reason = getUnbondReasonFromHALCode(msg.arg2);
sendIntent(dev, newState, reason);
if(newState != BluetoothDevice.BOND_BONDING )
{
/* this is either none/bonded, remove and transition */
result = !mDevices.remove(dev);
if (mDevices.isEmpty()) {
// Whenever mDevices is empty, then we need to
// set result=false. Else, we will end up adding
// the device to the list again. This prevents us
// from pairing with a device that we just unpaired
result = false;
transitionTo(mStableState);
}
if (newState == BluetoothDevice.BOND_NONE)
{
// Set the profile Priorities to undefined
clearProfilePriorty(dev);
}
else if (newState == BluetoothDevice.BOND_BONDED)
{
// Restore the profile priorty settings
setProfilePriorty(dev);
}
}
else if(!mDevices.contains(dev))
result=true;
break;
default:
Log.e(TAG, "Received unhandled event:" + msg.what);
return false;
}
if (result) mDevices.add(dev);
return true;
}
}
private boolean cancelBond(BluetoothDevice dev) {
if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
if (!mAdapterService.cancelBondNative(addr)) {
Log.e(TAG, "Unexpected error while cancelling bond:");
} else {
return true;
}
}
return false;
}
private boolean removeBond(BluetoothDevice dev, boolean transition) {
if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
if (!mAdapterService.removeBondNative(addr)) {
Log.e(TAG, "Unexpected error while removing bond:");
} else {
if (transition) transitionTo(mPendingCommandState);
return true;
}
}
return false;
}
private boolean createBond(BluetoothDevice dev, boolean transition) {
if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
infoLog("Bond address is:" + dev);
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
if (!mAdapterService.createBondNative(addr)) {
sendIntent(dev, BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOVED);
return false;
} else if (transition) {
transitionTo(mPendingCommandState);
}
return true;
}
return false;
}
private void sendIntent(BluetoothDevice device, int newState, int reason) {
DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
int oldState = BluetoothDevice.BOND_NONE;
if (devProp != null) {
oldState = devProp.getBondState();
}
if (oldState == newState) return;
mAdapterProperties.onBondStateChanged(device, newState);
Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
if (newState == BluetoothDevice.BOND_NONE)
intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
AdapterService.BLUETOOTH_PERM);
infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
+ " NewState: " + newState);
}
void bondStateChangeCallback(int status, byte[] address, int newState) {
BluetoothDevice device = mRemoteDevices.getDevice(address);
if (device == null) {
infoLog("No record of the device:" + device);
// This device will be added as part of the BONDING_STATE_CHANGE intent processing
// in sendIntent above
device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
}
infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
+ " newState: " + newState);
Message msg = obtainMessage(BONDING_STATE_CHANGE);
msg.obj = device;
if (newState == BOND_STATE_BONDED)
msg.arg1 = BluetoothDevice.BOND_BONDED;
else if (newState == BOND_STATE_BONDING)
msg.arg1 = BluetoothDevice.BOND_BONDING;
else
msg.arg1 = BluetoothDevice.BOND_NONE;
msg.arg2 = status;
sendMessage(msg);
}
private void setProfilePriorty (BluetoothDevice device){
HidService hidService = HidService.getHidService();
A2dpService a2dpService = A2dpService.getA2dpService();
HeadsetService headsetService = HeadsetService.getHeadsetService();
if ((hidService != null) &&
(hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
}
if ((a2dpService != null) &&
(a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
}
if ((headsetService != null) &&
(headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
}
}
private void clearProfilePriorty (BluetoothDevice device){
HidService hidService = HidService.getHidService();
A2dpService a2dpService = A2dpService.getA2dpService();
HeadsetService headsetService = HeadsetService.getHeadsetService();
if (hidService != null)
hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
if(a2dpService != null)
a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
if(headsetService != null)
headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
}
private void infoLog(String msg) {
Log.i(TAG, msg);
}
private void errorLog(String msg) {
Log.e(TAG, msg);
}
private int getUnbondReasonFromHALCode (int reason) {
if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
return BluetoothDevice.BOND_SUCCESS;
else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
/* default */
return BluetoothDevice.UNBOND_REASON_REMOVED;
}
}