BondStateMachine.java revision db681f3973bd8a088a878fd6f41d4330cdbc5522
1/* 2 * Copyright (C) 2012 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.bluetooth.btservice; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.BluetoothProfile; 21import android.bluetooth.BluetoothDevice; 22import com.android.bluetooth.a2dp.A2dpService; 23import com.android.bluetooth.hid.HidService; 24import com.android.bluetooth.hfp.HeadsetService; 25import android.content.Context; 26import android.content.Intent; 27import android.os.Message; 28import android.os.UserHandle; 29import android.util.Log; 30 31import com.android.bluetooth.Utils; 32import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; 33import com.android.internal.util.State; 34import com.android.internal.util.StateMachine; 35 36import java.util.ArrayList; 37 38/** 39 * This state machine handles Bluetooth Adapter State. 40 * States: 41 * {@link StableState} : No device is in bonding / unbonding state. 42 * {@link PendingCommandState} : Some device is in bonding / unbonding state. 43 * TODO(BT) This class can be removed and this logic moved to the stack. 44 */ 45 46final class BondStateMachine extends StateMachine { 47 private static final boolean DBG = false; 48 private static final String TAG = "BluetoothBondStateMachine"; 49 50 static final int CREATE_BOND = 1; 51 static final int CANCEL_BOND = 2; 52 static final int REMOVE_BOND = 3; 53 static final int BONDING_STATE_CHANGE = 4; 54 55 static final int BOND_STATE_NONE = 0; 56 static final int BOND_STATE_BONDING = 1; 57 static final int BOND_STATE_BONDED = 2; 58 59 private AdapterService mAdapterService; 60 private AdapterProperties mAdapterProperties; 61 private RemoteDevices mRemoteDevices; 62 private BluetoothAdapter mAdapter; 63 64 private PendingCommandState mPendingCommandState = new PendingCommandState(); 65 private StableState mStableState = new StableState(); 66 67 private BondStateMachine(AdapterService service, 68 AdapterProperties prop, RemoteDevices remoteDevices) { 69 super("BondStateMachine:"); 70 addState(mStableState); 71 addState(mPendingCommandState); 72 mRemoteDevices = remoteDevices; 73 mAdapterService = service; 74 mAdapterProperties = prop; 75 mAdapter = BluetoothAdapter.getDefaultAdapter(); 76 setInitialState(mStableState); 77 } 78 79 public static BondStateMachine make(AdapterService service, 80 AdapterProperties prop, RemoteDevices remoteDevices) { 81 Log.d(TAG, "make"); 82 BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices); 83 bsm.start(); 84 return bsm; 85 } 86 87 public void doQuit() { 88 quitNow(); 89 } 90 91 public void cleanup() { 92 mAdapterService = null; 93 mRemoteDevices = null; 94 mAdapterProperties = null; 95 } 96 97 private class StableState extends State { 98 @Override 99 public void enter() { 100 infoLog("StableState(): Entering Off State"); 101 } 102 103 @Override 104 public boolean processMessage(Message msg) { 105 106 BluetoothDevice dev = (BluetoothDevice)msg.obj; 107 108 switch(msg.what) { 109 110 case CREATE_BOND: 111 createBond(dev, msg.arg1, true); 112 break; 113 case REMOVE_BOND: 114 removeBond(dev, true); 115 break; 116 case BONDING_STATE_CHANGE: 117 int newState = msg.arg1; 118 /* if incoming pairing, transition to pending state */ 119 if (newState == BluetoothDevice.BOND_BONDING) 120 { 121 sendIntent(dev, newState, 0); 122 transitionTo(mPendingCommandState); 123 } 124 else 125 { 126 Log.e(TAG, "In stable state, received invalid newState: " + newState); 127 } 128 break; 129 130 case CANCEL_BOND: 131 default: 132 Log.e(TAG, "Received unhandled state: " + msg.what); 133 return false; 134 } 135 return true; 136 } 137 } 138 139 140 private class PendingCommandState extends State { 141 private final ArrayList<BluetoothDevice> mDevices = 142 new ArrayList<BluetoothDevice>(); 143 144 @Override 145 public void enter() { 146 infoLog("Entering PendingCommandState State"); 147 BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj; 148 } 149 150 @Override 151 public boolean processMessage(Message msg) { 152 153 BluetoothDevice dev = (BluetoothDevice)msg.obj; 154 boolean result = false; 155 if (mDevices.contains(dev) && 156 msg.what != CANCEL_BOND && msg.what != BONDING_STATE_CHANGE) { 157 deferMessage(msg); 158 return true; 159 } 160 161 switch (msg.what) { 162 case CREATE_BOND: 163 result = createBond(dev, msg.arg1, false); 164 break; 165 case REMOVE_BOND: 166 result = removeBond(dev, false); 167 break; 168 case CANCEL_BOND: 169 result = cancelBond(dev); 170 break; 171 case BONDING_STATE_CHANGE: 172 int newState = msg.arg1; 173 int reason = getUnbondReasonFromHALCode(msg.arg2); 174 sendIntent(dev, newState, reason); 175 if(newState != BluetoothDevice.BOND_BONDING ) 176 { 177 /* this is either none/bonded, remove and transition */ 178 result = !mDevices.remove(dev); 179 if (mDevices.isEmpty()) { 180 // Whenever mDevices is empty, then we need to 181 // set result=false. Else, we will end up adding 182 // the device to the list again. This prevents us 183 // from pairing with a device that we just unpaired 184 result = false; 185 transitionTo(mStableState); 186 } 187 if (newState == BluetoothDevice.BOND_NONE) 188 { 189 // Set the profile Priorities to undefined 190 clearProfilePriorty(dev); 191 } 192 else if (newState == BluetoothDevice.BOND_BONDED) 193 { 194 // Do not set profile priority 195 // Profile priority should be set after SDP completion 196 197 // Restore the profile priorty settings 198 //setProfilePriorty(dev); 199 } 200 } 201 else if(!mDevices.contains(dev)) 202 result=true; 203 break; 204 default: 205 Log.e(TAG, "Received unhandled event:" + msg.what); 206 return false; 207 } 208 if (result) mDevices.add(dev); 209 210 return true; 211 } 212 } 213 214 private boolean cancelBond(BluetoothDevice dev) { 215 if (dev.getBondState() == BluetoothDevice.BOND_BONDING) { 216 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 217 if (!mAdapterService.cancelBondNative(addr)) { 218 Log.e(TAG, "Unexpected error while cancelling bond:"); 219 } else { 220 return true; 221 } 222 } 223 return false; 224 } 225 226 private boolean removeBond(BluetoothDevice dev, boolean transition) { 227 if (dev.getBondState() == BluetoothDevice.BOND_BONDED) { 228 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 229 if (!mAdapterService.removeBondNative(addr)) { 230 Log.e(TAG, "Unexpected error while removing bond:"); 231 } else { 232 if (transition) transitionTo(mPendingCommandState); 233 return true; 234 } 235 236 } 237 return false; 238 } 239 240 private boolean createBond(BluetoothDevice dev, int transport, boolean transition) { 241 if (dev.getBondState() == BluetoothDevice.BOND_NONE) { 242 infoLog("Bond address is:" + dev); 243 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 244 if (!mAdapterService.createBondNative(addr, transport)) { 245 sendIntent(dev, BluetoothDevice.BOND_NONE, 246 BluetoothDevice.UNBOND_REASON_REMOVED); 247 return false; 248 } else if (transition) { 249 transitionTo(mPendingCommandState); 250 } 251 return true; 252 } 253 return false; 254 } 255 256 private void sendIntent(BluetoothDevice device, int newState, int reason) { 257 DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device); 258 int oldState = BluetoothDevice.BOND_NONE; 259 if (devProp != null) { 260 oldState = devProp.getBondState(); 261 } 262 if (oldState == newState) return; 263 mAdapterProperties.onBondStateChanged(device, newState); 264 265 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 266 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 267 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState); 268 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); 269 if (newState == BluetoothDevice.BOND_NONE) 270 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason); 271 mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, 272 AdapterService.BLUETOOTH_PERM); 273 infoLog("Bond State Change Intent:" + device + " OldState: " + oldState 274 + " NewState: " + newState); 275 } 276 277 void bondStateChangeCallback(int status, byte[] address, int newState) { 278 BluetoothDevice device = mRemoteDevices.getDevice(address); 279 280 if (device == null) { 281 infoLog("No record of the device:" + device); 282 // This device will be added as part of the BONDING_STATE_CHANGE intent processing 283 // in sendIntent above 284 device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 285 } 286 287 infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device 288 + " newState: " + newState); 289 290 Message msg = obtainMessage(BONDING_STATE_CHANGE); 291 msg.obj = device; 292 293 if (newState == BOND_STATE_BONDED) 294 msg.arg1 = BluetoothDevice.BOND_BONDED; 295 else if (newState == BOND_STATE_BONDING) 296 msg.arg1 = BluetoothDevice.BOND_BONDING; 297 else 298 msg.arg1 = BluetoothDevice.BOND_NONE; 299 msg.arg2 = status; 300 301 sendMessage(msg); 302 } 303 304 private void setProfilePriorty (BluetoothDevice device){ 305 HidService hidService = HidService.getHidService(); 306 A2dpService a2dpService = A2dpService.getA2dpService(); 307 HeadsetService headsetService = HeadsetService.getHeadsetService(); 308 309 if ((hidService != null) && 310 (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 311 hidService.setPriority(device,BluetoothProfile.PRIORITY_ON); 312 } 313 314 if ((a2dpService != null) && 315 (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 316 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON); 317 } 318 319 if ((headsetService != null) && 320 (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 321 headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON); 322 } 323 } 324 325 private void clearProfilePriorty (BluetoothDevice device){ 326 HidService hidService = HidService.getHidService(); 327 A2dpService a2dpService = A2dpService.getA2dpService(); 328 HeadsetService headsetService = HeadsetService.getHeadsetService(); 329 330 if (hidService != null) 331 hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 332 if(a2dpService != null) 333 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 334 if(headsetService != null) 335 headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 336 } 337 338 private void infoLog(String msg) { 339 Log.i(TAG, msg); 340 } 341 342 private void errorLog(String msg) { 343 Log.e(TAG, msg); 344 } 345 346 private int getUnbondReasonFromHALCode (int reason) { 347 if (reason == AbstractionLayer.BT_STATUS_SUCCESS) 348 return BluetoothDevice.BOND_SUCCESS; 349 else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) 350 return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN; 351 else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) 352 return BluetoothDevice.UNBOND_REASON_AUTH_FAILED; 353 else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) 354 return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED; 355 else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) 356 return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT; 357 358 /* default */ 359 return BluetoothDevice.UNBOND_REASON_REMOVED; 360 } 361} 362