1/* 2 * Copyright (C) 2011 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 android.server; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.IBluetoothStateChangeCallback; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.os.Binder; 25import android.os.Message; 26import android.os.RemoteException; 27import android.provider.Settings; 28import android.util.Log; 29 30import com.android.internal.util.IState; 31import com.android.internal.util.State; 32import com.android.internal.util.StateMachine; 33 34import java.io.PrintWriter; 35 36/** 37 * Bluetooth Adapter StateMachine 38 * All the states are at the same level, ie, no hierarchy. 39 * (BluetootOn)<----------------------<- 40 * | ^ -------------------->- | 41 * | | | | 42 * USER_TURN_OFF | | SCAN_MODE_CHANGED m1 | | USER_TURN_ON 43 * AIRPLANE_MODE_ON | | | | 44 * V | | | 45 * (Switching) (PerProcessState) 46 * | ^ | | 47 * POWER_STATE_CHANGED & | | TURN_ON(_CONTINUE) | | 48 * ALL_DEVICES_DISCONNECTED | | m2 | | 49 * V |------------------------< | SCAN_MODE_CHANGED 50 * (HotOff)-------------------------->- PER_PROCESS_TURN_ON 51 * / ^ 52 * / | SERVICE_RECORD_LOADED 53 * | | 54 * TURN_COLD | (Warmup) 55 * \ ^ 56 * \ | TURN_HOT/TURN_ON 57 * | | AIRPLANE_MODE_OFF(when Bluetooth was on before) 58 * V | 59 * (PowerOff) <----- initial state 60 * 61 * Legend: 62 * m1 = TURN_HOT 63 * m2 = Transition to HotOff when number of process wanting BT on is 0. 64 * POWER_STATE_CHANGED will make the transition. 65 * Note: 66 * The diagram above shows all the states and messages that trigger normal state changes. 67 * The diagram above does not capture everything: 68 * The diagram does not capture following messages. 69 * - messages that do not trigger state changes 70 * For example, PER_PROCESS_TURN_ON received in BluetoothOn state 71 * - unhandled messages 72 * For example, USER_TURN_ON received in BluetoothOn state 73 * - timeout messages 74 * The diagram does not capture error conditions and state recoveries. 75 * - For example POWER_STATE_CHANGED received in BluetoothOn state 76 */ 77final class BluetoothAdapterStateMachine extends StateMachine { 78 private static final String TAG = "BluetoothAdapterStateMachine"; 79 private static final boolean DBG = false; 80 81 // Message(what) to take an action 82 // 83 // We get this message when user tries to turn on BT 84 static final int USER_TURN_ON = 1; 85 // We get this message when user tries to turn off BT 86 static final int USER_TURN_OFF = 2; 87 // Per process enable / disable messages 88 static final int PER_PROCESS_TURN_ON = 3; 89 static final int PER_PROCESS_TURN_OFF = 4; 90 91 // Turn on Bluetooth Module, Load firmware, and do all the preparation 92 // needed to get the Bluetooth Module ready but keep it not discoverable 93 // and not connectable. This way the Bluetooth Module can be quickly 94 // switched on if needed 95 static final int TURN_HOT = 5; 96 97 // Message(what) to report a event that the state machine need to respond to 98 // 99 // Event indicates sevice records have been loaded 100 static final int SERVICE_RECORD_LOADED = 51; 101 // Event indicates all the remote Bluetooth devices has been disconnected 102 static final int ALL_DEVICES_DISCONNECTED = 52; 103 // Event indicates the Bluetooth scan mode has changed 104 static final int SCAN_MODE_CHANGED = 53; 105 // Event indicates the powered state has changed 106 static final int POWER_STATE_CHANGED = 54; 107 // Event indicates airplane mode is turned on 108 static final int AIRPLANE_MODE_ON = 55; 109 // Event indicates airplane mode is turned off 110 static final int AIRPLANE_MODE_OFF = 56; 111 112 // private internal messages 113 // 114 // USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the 115 // state change intent so that we will not broadcast the intent again in 116 // other state 117 private static final int TURN_ON_CONTINUE = 101; 118 // Unload firmware, turning off Bluetooth module power 119 private static final int TURN_COLD = 102; 120 // Device disconnecting timeout happens 121 private static final int DEVICES_DISCONNECT_TIMEOUT = 103; 122 // Prepare Bluetooth timeout happens 123 private static final int PREPARE_BLUETOOTH_TIMEOUT = 104; 124 // Bluetooth turn off wait timeout happens 125 private static final int TURN_OFF_TIMEOUT = 105; 126 // Bluetooth device power off wait timeout happens 127 private static final int POWER_DOWN_TIMEOUT = 106; 128 129 private Context mContext; 130 private BluetoothService mBluetoothService; 131 private BluetoothEventLoop mEventLoop; 132 133 private BluetoothOn mBluetoothOn; 134 private Switching mSwitching; 135 private HotOff mHotOff; 136 private WarmUp mWarmUp; 137 private PowerOff mPowerOff; 138 private PerProcessState mPerProcessState; 139 140 // this is the BluetoothAdapter state that reported externally 141 private int mPublicState; 142 // When turning off, broadcast STATE_OFF in the last HotOff state 143 // This is because we do HotOff -> PowerOff -> HotOff for USER_TURN_OFF 144 private boolean mDelayBroadcastStateOff; 145 146 // timeout value waiting for all the devices to be disconnected 147 private static final int DEVICES_DISCONNECT_TIMEOUT_TIME = 3000; 148 149 private static final int PREPARE_BLUETOOTH_TIMEOUT_TIME = 10000; 150 151 private static final int TURN_OFF_TIMEOUT_TIME = 5000; 152 private static final int POWER_DOWN_TIMEOUT_TIME = 20; 153 154 BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService, 155 BluetoothAdapter bluetoothAdapter) { 156 super(TAG); 157 mContext = context; 158 mBluetoothService = bluetoothService; 159 mEventLoop = new BluetoothEventLoop(context, bluetoothAdapter, bluetoothService, this); 160 161 mBluetoothOn = new BluetoothOn(); 162 mSwitching = new Switching(); 163 mHotOff = new HotOff(); 164 mWarmUp = new WarmUp(); 165 mPowerOff = new PowerOff(); 166 mPerProcessState = new PerProcessState(); 167 168 addState(mBluetoothOn); 169 addState(mSwitching); 170 addState(mHotOff); 171 addState(mWarmUp); 172 addState(mPowerOff); 173 addState(mPerProcessState); 174 175 setInitialState(mPowerOff); 176 mPublicState = BluetoothAdapter.STATE_OFF; 177 mDelayBroadcastStateOff = false; 178 } 179 180 /** 181 * Bluetooth module's power is off, firmware is not loaded. 182 */ 183 private class PowerOff extends State { 184 @Override 185 public void enter() { 186 if (DBG) log("Enter PowerOff: " + getCurrentMessage().what); 187 } 188 @Override 189 public boolean processMessage(Message message) { 190 log("PowerOff process message: " + message.what); 191 192 boolean retValue = HANDLED; 193 switch(message.what) { 194 case USER_TURN_ON: 195 // starts turning on BT module, broadcast this out 196 broadcastState(BluetoothAdapter.STATE_TURNING_ON); 197 transitionTo(mWarmUp); 198 if (prepareBluetooth()) { 199 // this is user request, save the setting 200 if ((Boolean) message.obj) { 201 persistSwitchSetting(true); 202 } 203 // We will continue turn the BT on all the way to the BluetoothOn state 204 deferMessage(obtainMessage(TURN_ON_CONTINUE)); 205 } else { 206 Log.e(TAG, "failed to prepare bluetooth, abort turning on"); 207 transitionTo(mPowerOff); 208 broadcastState(BluetoothAdapter.STATE_OFF); 209 } 210 break; 211 case TURN_HOT: 212 if (prepareBluetooth()) { 213 transitionTo(mWarmUp); 214 } 215 break; 216 case AIRPLANE_MODE_OFF: 217 if (getBluetoothPersistedSetting()) { 218 // starts turning on BT module, broadcast this out 219 broadcastState(BluetoothAdapter.STATE_TURNING_ON); 220 transitionTo(mWarmUp); 221 if (prepareBluetooth()) { 222 // We will continue turn the BT on all the way to the BluetoothOn state 223 deferMessage(obtainMessage(TURN_ON_CONTINUE)); 224 transitionTo(mWarmUp); 225 } else { 226 Log.e(TAG, "failed to prepare bluetooth, abort turning on"); 227 transitionTo(mPowerOff); 228 broadcastState(BluetoothAdapter.STATE_OFF); 229 } 230 } else if (mContext.getResources().getBoolean 231 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { 232 sendMessage(TURN_HOT); 233 } 234 break; 235 case PER_PROCESS_TURN_ON: 236 if (prepareBluetooth()) { 237 transitionTo(mWarmUp); 238 } 239 deferMessage(obtainMessage(PER_PROCESS_TURN_ON)); 240 break; 241 case PER_PROCESS_TURN_OFF: 242 perProcessCallback(false, (IBluetoothStateChangeCallback) message.obj); 243 break; 244 case USER_TURN_OFF: 245 Log.w(TAG, "PowerOff received: " + message.what); 246 case AIRPLANE_MODE_ON: // ignore 247 break; 248 default: 249 return NOT_HANDLED; 250 } 251 return retValue; 252 } 253 254 /** 255 * Turn on Bluetooth Module, Load firmware, and do all the preparation 256 * needed to get the Bluetooth Module ready but keep it not discoverable 257 * and not connectable. 258 * The last step of this method sets up the local service record DB. 259 * There will be a event reporting the status of the SDP setup. 260 */ 261 private boolean prepareBluetooth() { 262 if (mBluetoothService.enableNative() != 0) { 263 return false; 264 } 265 266 // try to start event loop, give 2 attempts 267 int retryCount = 2; 268 boolean eventLoopStarted = false; 269 while ((retryCount-- > 0) && !eventLoopStarted) { 270 mEventLoop.start(); 271 // it may take a moment for the other thread to do its 272 // thing. Check periodically for a while. 273 int pollCount = 5; 274 while ((pollCount-- > 0) && !eventLoopStarted) { 275 if (mEventLoop.isEventLoopRunning()) { 276 eventLoopStarted = true; 277 break; 278 } 279 try { 280 Thread.sleep(100); 281 } catch (InterruptedException e) { 282 log("prepareBluetooth sleep interrupted: " + pollCount); 283 break; 284 } 285 } 286 } 287 288 if (!eventLoopStarted) { 289 mBluetoothService.disableNative(); 290 return false; 291 } 292 293 // get BluetoothService ready 294 if (!mBluetoothService.prepareBluetooth()) { 295 mEventLoop.stop(); 296 mBluetoothService.disableNative(); 297 return false; 298 } 299 300 sendMessageDelayed(PREPARE_BLUETOOTH_TIMEOUT, PREPARE_BLUETOOTH_TIMEOUT_TIME); 301 return true; 302 } 303 } 304 305 /** 306 * Turning on Bluetooth module's power, loading firmware, starting 307 * event loop thread to listen on Bluetooth module event changes. 308 */ 309 private class WarmUp extends State { 310 311 @Override 312 public void enter() { 313 if (DBG) log("Enter WarmUp: " + getCurrentMessage().what); 314 } 315 316 @Override 317 public boolean processMessage(Message message) { 318 log("WarmUp process message: " + message.what); 319 320 boolean retValue = HANDLED; 321 switch(message.what) { 322 case SERVICE_RECORD_LOADED: 323 removeMessages(PREPARE_BLUETOOTH_TIMEOUT); 324 transitionTo(mHotOff); 325 if (mDelayBroadcastStateOff) { 326 broadcastState(BluetoothAdapter.STATE_OFF); 327 mDelayBroadcastStateOff = false; 328 } 329 break; 330 case PREPARE_BLUETOOTH_TIMEOUT: 331 Log.e(TAG, "Bluetooth adapter SDP failed to load"); 332 shutoffBluetooth(); 333 transitionTo(mPowerOff); 334 broadcastState(BluetoothAdapter.STATE_OFF); 335 break; 336 case USER_TURN_ON: // handle this at HotOff state 337 case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth 338 // on to the BluetoothOn state 339 case AIRPLANE_MODE_ON: 340 case AIRPLANE_MODE_OFF: 341 case PER_PROCESS_TURN_ON: 342 case PER_PROCESS_TURN_OFF: 343 deferMessage(message); 344 break; 345 case USER_TURN_OFF: 346 Log.w(TAG, "WarmUp received: " + message.what); 347 break; 348 default: 349 return NOT_HANDLED; 350 } 351 return retValue; 352 } 353 354 } 355 356 /** 357 * Bluetooth Module has powered, firmware loaded, event loop started, 358 * SDP loaded, but the modules stays non-discoverable and 359 * non-connectable. 360 */ 361 private class HotOff extends State { 362 @Override 363 public void enter() { 364 if (DBG) log("Enter HotOff: " + getCurrentMessage().what); 365 } 366 367 @Override 368 public boolean processMessage(Message message) { 369 log("HotOff process message: " + message.what); 370 371 boolean retValue = HANDLED; 372 switch(message.what) { 373 case USER_TURN_ON: 374 broadcastState(BluetoothAdapter.STATE_TURNING_ON); 375 if ((Boolean) message.obj) { 376 persistSwitchSetting(true); 377 } 378 // let it fall to TURN_ON_CONTINUE: 379 //$FALL-THROUGH$ 380 case TURN_ON_CONTINUE: 381 mBluetoothService.switchConnectable(true); 382 transitionTo(mSwitching); 383 break; 384 case AIRPLANE_MODE_ON: 385 case TURN_COLD: 386 shutoffBluetooth(); 387 // we cannot go to power off state yet, we need wait for the Bluetooth 388 // device power off. Unfortunately the stack does not give a event back 389 // so we wait a little bit here 390 sendMessageDelayed(POWER_DOWN_TIMEOUT, 391 POWER_DOWN_TIMEOUT_TIME); 392 break; 393 case POWER_DOWN_TIMEOUT: 394 transitionTo(mPowerOff); 395 if (!mDelayBroadcastStateOff) { 396 broadcastState(BluetoothAdapter.STATE_OFF); 397 } 398 break; 399 case AIRPLANE_MODE_OFF: 400 if (getBluetoothPersistedSetting()) { 401 broadcastState(BluetoothAdapter.STATE_TURNING_ON); 402 transitionTo(mSwitching); 403 mBluetoothService.switchConnectable(true); 404 } 405 break; 406 case PER_PROCESS_TURN_ON: 407 transitionTo(mPerProcessState); 408 409 // Resend the PER_PROCESS_TURN_ON message so that the callback 410 // can be sent through. 411 deferMessage(message); 412 413 mBluetoothService.switchConnectable(true); 414 break; 415 case PER_PROCESS_TURN_OFF: 416 perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); 417 break; 418 case USER_TURN_OFF: // ignore 419 break; 420 case POWER_STATE_CHANGED: 421 if ((Boolean) message.obj) { 422 recoverStateMachine(TURN_HOT, null); 423 } 424 break; 425 case TURN_HOT: 426 deferMessage(message); 427 break; 428 default: 429 return NOT_HANDLED; 430 } 431 return retValue; 432 } 433 434 } 435 436 private class Switching extends State { 437 438 @Override 439 public void enter() { 440 if (DBG) log("Enter Switching: " + getCurrentMessage().what); 441 } 442 @Override 443 public boolean processMessage(Message message) { 444 log("Switching process message: " + message.what); 445 446 boolean retValue = HANDLED; 447 switch(message.what) { 448 case SCAN_MODE_CHANGED: 449 // This event matches mBluetoothService.switchConnectable action 450 if (mPublicState == BluetoothAdapter.STATE_TURNING_ON) { 451 // set pairable if it's not 452 mBluetoothService.setPairable(); 453 mBluetoothService.initBluetoothAfterTurningOn(); 454 transitionTo(mBluetoothOn); 455 broadcastState(BluetoothAdapter.STATE_ON); 456 // run bluetooth now that it's turned on 457 // Note runBluetooth should be called only in adapter STATE_ON 458 mBluetoothService.runBluetooth(); 459 } 460 break; 461 case POWER_STATE_CHANGED: 462 removeMessages(TURN_OFF_TIMEOUT); 463 if (!((Boolean) message.obj)) { 464 if (mPublicState == BluetoothAdapter.STATE_TURNING_OFF) { 465 transitionTo(mHotOff); 466 mBluetoothService.finishDisable(); 467 mBluetoothService.cleanupAfterFinishDisable(); 468 deferMessage(obtainMessage(TURN_COLD)); 469 if (mContext.getResources().getBoolean 470 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch) && 471 !mBluetoothService.isAirplaneModeOn()) { 472 deferMessage(obtainMessage(TURN_HOT)); 473 mDelayBroadcastStateOff = true; 474 } 475 } 476 } else { 477 if (mPublicState != BluetoothAdapter.STATE_TURNING_ON) { 478 if (mContext.getResources().getBoolean 479 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { 480 recoverStateMachine(TURN_HOT, null); 481 } else { 482 recoverStateMachine(TURN_COLD, null); 483 } 484 } 485 } 486 break; 487 case ALL_DEVICES_DISCONNECTED: 488 removeMessages(DEVICES_DISCONNECT_TIMEOUT); 489 mBluetoothService.switchConnectable(false); 490 sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); 491 break; 492 case DEVICES_DISCONNECT_TIMEOUT: 493 sendMessage(ALL_DEVICES_DISCONNECTED); 494 // reset the hardware for error recovery 495 Log.e(TAG, "Devices failed to disconnect, reseting..."); 496 deferMessage(obtainMessage(TURN_COLD)); 497 if (mContext.getResources().getBoolean 498 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { 499 deferMessage(obtainMessage(TURN_HOT)); 500 } 501 break; 502 case TURN_OFF_TIMEOUT: 503 transitionTo(mHotOff); 504 finishSwitchingOff(); 505 // reset the hardware for error recovery 506 Log.e(TAG, "Devices failed to power down, reseting..."); 507 deferMessage(obtainMessage(TURN_COLD)); 508 if (mContext.getResources().getBoolean 509 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { 510 deferMessage(obtainMessage(TURN_HOT)); 511 } 512 break; 513 case USER_TURN_ON: 514 case AIRPLANE_MODE_OFF: 515 case AIRPLANE_MODE_ON: 516 case PER_PROCESS_TURN_ON: 517 case PER_PROCESS_TURN_OFF: 518 case USER_TURN_OFF: 519 deferMessage(message); 520 break; 521 522 default: 523 return NOT_HANDLED; 524 } 525 return retValue; 526 } 527 } 528 529 private class BluetoothOn extends State { 530 531 @Override 532 public void enter() { 533 if (DBG) log("Enter BluetoothOn: " + getCurrentMessage().what); 534 } 535 @Override 536 public boolean processMessage(Message message) { 537 log("BluetoothOn process message: " + message.what); 538 539 boolean retValue = HANDLED; 540 switch(message.what) { 541 case USER_TURN_OFF: 542 if ((Boolean) message.obj) { 543 persistSwitchSetting(false); 544 } 545 546 if (mBluetoothService.isDiscovering()) { 547 mBluetoothService.cancelDiscovery(); 548 } 549 if (!mBluetoothService.isApplicationStateChangeTrackerEmpty()) { 550 transitionTo(mPerProcessState); 551 deferMessage(obtainMessage(TURN_HOT)); 552 break; 553 } 554 //$FALL-THROUGH$ to AIRPLANE_MODE_ON 555 case AIRPLANE_MODE_ON: 556 broadcastState(BluetoothAdapter.STATE_TURNING_OFF); 557 transitionTo(mSwitching); 558 if (mBluetoothService.getAdapterConnectionState() != 559 BluetoothAdapter.STATE_DISCONNECTED) { 560 mBluetoothService.disconnectDevices(); 561 sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT, 562 DEVICES_DISCONNECT_TIMEOUT_TIME); 563 } else { 564 mBluetoothService.switchConnectable(false); 565 sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); 566 } 567 568 if (message.what == AIRPLANE_MODE_ON || mBluetoothService.isAirplaneModeOn()) { 569 // We inform all the per process callbacks 570 allProcessesCallback(false); 571 } 572 break; 573 case AIRPLANE_MODE_OFF: 574 case USER_TURN_ON: 575 Log.w(TAG, "BluetoothOn received: " + message.what); 576 break; 577 case PER_PROCESS_TURN_ON: 578 perProcessCallback(true, (IBluetoothStateChangeCallback)message.obj); 579 break; 580 case PER_PROCESS_TURN_OFF: 581 perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); 582 break; 583 case POWER_STATE_CHANGED: 584 if ((Boolean) message.obj) { 585 // reset the state machine and send it TURN_ON_CONTINUE message 586 recoverStateMachine(USER_TURN_ON, false); 587 } 588 break; 589 default: 590 return NOT_HANDLED; 591 } 592 return retValue; 593 } 594 595 } 596 597 598 private class PerProcessState extends State { 599 IBluetoothStateChangeCallback mCallback = null; 600 boolean isTurningOn = false; 601 602 @Override 603 public void enter() { 604 int what = getCurrentMessage().what; 605 if (DBG) log("Enter PerProcessState: " + what); 606 607 if (what == PER_PROCESS_TURN_ON) { 608 isTurningOn = true; 609 } else if (what == USER_TURN_OFF) { 610 isTurningOn = false; 611 } else { 612 Log.e(TAG, "enter PerProcessState: wrong msg: " + what); 613 } 614 } 615 616 @Override 617 public boolean processMessage(Message message) { 618 log("PerProcessState process message: " + message.what); 619 620 boolean retValue = HANDLED; 621 switch (message.what) { 622 case PER_PROCESS_TURN_ON: 623 mCallback = (IBluetoothStateChangeCallback)getCurrentMessage().obj; 624 625 // If this is not the first application call the callback. 626 if (mBluetoothService.getNumberOfApplicationStateChangeTrackers() > 1) { 627 perProcessCallback(true, mCallback); 628 } 629 break; 630 case SCAN_MODE_CHANGED: 631 if (isTurningOn) { 632 perProcessCallback(true, mCallback); 633 isTurningOn = false; 634 } 635 break; 636 case POWER_STATE_CHANGED: 637 removeMessages(TURN_OFF_TIMEOUT); 638 if (!((Boolean) message.obj)) { 639 transitionTo(mHotOff); 640 if (!mContext.getResources().getBoolean 641 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { 642 deferMessage(obtainMessage(TURN_COLD)); 643 } 644 } else { 645 if (!isTurningOn) { 646 recoverStateMachine(TURN_COLD, null); 647 for (IBluetoothStateChangeCallback c: 648 mBluetoothService.getApplicationStateChangeCallbacks()) { 649 perProcessCallback(false, c); 650 deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c)); 651 } 652 } 653 } 654 break; 655 case TURN_OFF_TIMEOUT: 656 transitionTo(mHotOff); 657 Log.e(TAG, "Power-down timed out, resetting..."); 658 deferMessage(obtainMessage(TURN_COLD)); 659 if (mContext.getResources().getBoolean 660 (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { 661 deferMessage(obtainMessage(TURN_HOT)); 662 } 663 break; 664 case USER_TURN_ON: 665 broadcastState(BluetoothAdapter.STATE_TURNING_ON); 666 persistSwitchSetting(true); 667 mBluetoothService.initBluetoothAfterTurningOn(); 668 transitionTo(mBluetoothOn); 669 broadcastState(BluetoothAdapter.STATE_ON); 670 // run bluetooth now that it's turned on 671 mBluetoothService.runBluetooth(); 672 break; 673 case TURN_HOT: 674 broadcastState(BluetoothAdapter.STATE_TURNING_OFF); 675 if (mBluetoothService.getAdapterConnectionState() != 676 BluetoothAdapter.STATE_DISCONNECTED) { 677 mBluetoothService.disconnectDevices(); 678 sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT, 679 DEVICES_DISCONNECT_TIMEOUT_TIME); 680 break; 681 } 682 //$FALL-THROUGH$ all devices are already disconnected 683 case ALL_DEVICES_DISCONNECTED: 684 removeMessages(DEVICES_DISCONNECT_TIMEOUT); 685 finishSwitchingOff(); 686 break; 687 case DEVICES_DISCONNECT_TIMEOUT: 688 finishSwitchingOff(); 689 Log.e(TAG, "Devices fail to disconnect, reseting..."); 690 transitionTo(mHotOff); 691 deferMessage(obtainMessage(TURN_COLD)); 692 for (IBluetoothStateChangeCallback c: 693 mBluetoothService.getApplicationStateChangeCallbacks()) { 694 perProcessCallback(false, c); 695 deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c)); 696 } 697 break; 698 case PER_PROCESS_TURN_OFF: 699 perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); 700 if (mBluetoothService.isApplicationStateChangeTrackerEmpty()) { 701 mBluetoothService.switchConnectable(false); 702 sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); 703 } 704 break; 705 case AIRPLANE_MODE_ON: 706 mBluetoothService.switchConnectable(false); 707 sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); 708 allProcessesCallback(false); 709 break; 710 case USER_TURN_OFF: 711 Log.w(TAG, "PerProcessState received: " + message.what); 712 break; 713 default: 714 return NOT_HANDLED; 715 } 716 return retValue; 717 } 718 } 719 720 private void finishSwitchingOff() { 721 mBluetoothService.finishDisable(); 722 broadcastState(BluetoothAdapter.STATE_OFF); 723 mBluetoothService.cleanupAfterFinishDisable(); 724 } 725 726 private void shutoffBluetooth() { 727 mBluetoothService.shutoffBluetooth(); 728 mEventLoop.stop(); 729 mBluetoothService.cleanNativeAfterShutoffBluetooth(); 730 } 731 732 private void perProcessCallback(boolean on, IBluetoothStateChangeCallback c) { 733 if (c == null) return; 734 735 try { 736 c.onBluetoothStateChange(on); 737 } catch (RemoteException e) {} 738 } 739 740 private void allProcessesCallback(boolean on) { 741 for (IBluetoothStateChangeCallback c: 742 mBluetoothService.getApplicationStateChangeCallbacks()) { 743 perProcessCallback(on, c); 744 } 745 if (!on) { 746 mBluetoothService.clearApplicationStateChangeTracker(); 747 } 748 } 749 750 /** 751 * Return the public BluetoothAdapter state 752 */ 753 int getBluetoothAdapterState() { 754 return mPublicState; 755 } 756 757 BluetoothEventLoop getBluetoothEventLoop() { 758 return mEventLoop; 759 } 760 761 private void persistSwitchSetting(boolean setOn) { 762 long origCallerIdentityToken = Binder.clearCallingIdentity(); 763 Settings.Secure.putInt(mContext.getContentResolver(), 764 Settings.Secure.BLUETOOTH_ON, 765 setOn ? 1 : 0); 766 Binder.restoreCallingIdentity(origCallerIdentityToken); 767 } 768 769 private boolean getBluetoothPersistedSetting() { 770 ContentResolver contentResolver = mContext.getContentResolver(); 771 return (Settings.Secure.getInt(contentResolver, 772 Settings.Secure.BLUETOOTH_ON, 0) > 0); 773 } 774 775 private void broadcastState(int newState) { 776 777 log("Bluetooth state " + mPublicState + " -> " + newState); 778 if (mPublicState == newState) { 779 return; 780 } 781 782 Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); 783 intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mPublicState); 784 intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); 785 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 786 mPublicState = newState; 787 788 mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); 789 } 790 791 /** 792 * bluetoothd has crashed and recovered, the adapter state machine has to 793 * reset itself and try to return to previous state 794 */ 795 private void recoverStateMachine(int what, Object obj) { 796 Log.e(TAG, "Get unexpected power on event, reset with: " + what); 797 transitionTo(mHotOff); 798 deferMessage(obtainMessage(TURN_COLD)); 799 deferMessage(obtainMessage(what, obj)); 800 } 801 802 private void dump(PrintWriter pw) { 803 IState currentState = getCurrentState(); 804 if (currentState == mPowerOff) { 805 pw.println("Bluetooth OFF - power down\n"); 806 } else if (currentState == mWarmUp) { 807 pw.println("Bluetooth OFF - warm up\n"); 808 } else if (currentState == mHotOff) { 809 pw.println("Bluetooth OFF - hot but off\n"); 810 } else if (currentState == mSwitching) { 811 pw.println("Bluetooth Switching\n"); 812 } else if (currentState == mBluetoothOn) { 813 pw.println("Bluetooth ON\n"); 814 } else { 815 pw.println("ERROR: Bluetooth UNKNOWN STATE "); 816 } 817 } 818 819 private static void log(String msg) { 820 Log.d(TAG, msg); 821 } 822} 823