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