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 17 package com.android.bluetooth.btservice; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothProfile; 21 import android.bluetooth.BluetoothDevice; 22 import com.android.bluetooth.a2dp.A2dpService; 23 import com.android.bluetooth.hid.HidService; 24 import com.android.bluetooth.hfp.HeadsetService; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.Message; 28 import android.os.UserHandle; 29 import android.util.Log; 30 31 import com.android.bluetooth.Utils; 32 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; 33 import com.android.internal.util.State; 34 import com.android.internal.util.StateMachine; 35 36 import 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 46 final 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, 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, 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 // Restore the profile priorty settings 195 setProfilePriorty(dev); 196 } 197 } 198 else if(!mDevices.contains(dev)) 199 result=true; 200 break; 201 default: 202 Log.e(TAG, "Received unhandled event:" + msg.what); 203 return false; 204 } 205 if (result) mDevices.add(dev); 206 207 return true; 208 } 209 } 210 211 private boolean cancelBond(BluetoothDevice dev) { 212 if (dev.getBondState() == BluetoothDevice.BOND_BONDING) { 213 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 214 if (!mAdapterService.cancelBondNative(addr)) { 215 Log.e(TAG, "Unexpected error while cancelling bond:"); 216 } else { 217 return true; 218 } 219 } 220 return false; 221 } 222 223 private boolean removeBond(BluetoothDevice dev, boolean transition) { 224 if (dev.getBondState() == BluetoothDevice.BOND_BONDED) { 225 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 226 if (!mAdapterService.removeBondNative(addr)) { 227 Log.e(TAG, "Unexpected error while removing bond:"); 228 } else { 229 if (transition) transitionTo(mPendingCommandState); 230 return true; 231 } 232 233 } 234 return false; 235 } 236 237 private boolean createBond(BluetoothDevice dev, boolean transition) { 238 if (dev.getBondState() == BluetoothDevice.BOND_NONE) { 239 infoLog("Bond address is:" + dev); 240 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 241 if (!mAdapterService.createBondNative(addr)) { 242 sendIntent(dev, BluetoothDevice.BOND_NONE, 243 BluetoothDevice.UNBOND_REASON_REMOVED); 244 return false; 245 } else if (transition) { 246 transitionTo(mPendingCommandState); 247 } 248 return true; 249 } 250 return false; 251 } 252 253 private void sendIntent(BluetoothDevice device, int newState, int reason) { 254 DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device); 255 int oldState = BluetoothDevice.BOND_NONE; 256 if (devProp != null) { 257 oldState = devProp.getBondState(); 258 } 259 if (oldState == newState) return; 260 mAdapterProperties.onBondStateChanged(device, newState); 261 262 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 263 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 264 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState); 265 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); 266 if (newState == BluetoothDevice.BOND_NONE) 267 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason); 268 mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, 269 AdapterService.BLUETOOTH_PERM); 270 infoLog("Bond State Change Intent:" + device + " OldState: " + oldState 271 + " NewState: " + newState); 272 } 273 274 void bondStateChangeCallback(int status, byte[] address, int newState) { 275 BluetoothDevice device = mRemoteDevices.getDevice(address); 276 277 if (device == null) { 278 infoLog("No record of the device:" + device); 279 // This device will be added as part of the BONDING_STATE_CHANGE intent processing 280 // in sendIntent above 281 device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 282 } 283 284 infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device 285 + " newState: " + newState); 286 287 Message msg = obtainMessage(BONDING_STATE_CHANGE); 288 msg.obj = device; 289 290 if (newState == BOND_STATE_BONDED) 291 msg.arg1 = BluetoothDevice.BOND_BONDED; 292 else if (newState == BOND_STATE_BONDING) 293 msg.arg1 = BluetoothDevice.BOND_BONDING; 294 else 295 msg.arg1 = BluetoothDevice.BOND_NONE; 296 msg.arg2 = status; 297 298 sendMessage(msg); 299 } 300 301 private void setProfilePriorty (BluetoothDevice device){ 302 HidService hidService = HidService.getHidService(); 303 A2dpService a2dpService = A2dpService.getA2dpService(); 304 HeadsetService headsetService = HeadsetService.getHeadsetService(); 305 306 if ((hidService != null) && 307 (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 308 hidService.setPriority(device,BluetoothProfile.PRIORITY_ON); 309 } 310 311 if ((a2dpService != null) && 312 (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 313 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON); 314 } 315 316 if ((headsetService != null) && 317 (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 318 headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON); 319 } 320 } 321 322 private void clearProfilePriorty (BluetoothDevice device){ 323 HidService hidService = HidService.getHidService(); 324 A2dpService a2dpService = A2dpService.getA2dpService(); 325 HeadsetService headsetService = HeadsetService.getHeadsetService(); 326 327 if (hidService != null) 328 hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 329 if(a2dpService != null) 330 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 331 if(headsetService != null) 332 headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 333 } 334 335 private void infoLog(String msg) { 336 Log.i(TAG, msg); 337 } 338 339 private void errorLog(String msg) { 340 Log.e(TAG, msg); 341 } 342 343 private int getUnbondReasonFromHALCode (int reason) { 344 if (reason == AbstractionLayer.BT_STATUS_SUCCESS) 345 return BluetoothDevice.BOND_SUCCESS; 346 else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) 347 return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN; 348 else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) 349 return BluetoothDevice.UNBOND_REASON_AUTH_FAILED; 350 else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) 351 return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED; 352 else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) 353 return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT; 354 355 /* default */ 356 return BluetoothDevice.UNBOND_REASON_REMOVED; 357 } 358 } 359