Home | History | Annotate | Download | only in btservice
      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.content.Context;
     21 import android.content.Intent;
     22 import android.os.Message;
     23 import android.util.Log;
     24 
     25 import com.android.internal.util.State;
     26 import com.android.internal.util.StateMachine;
     27 
     28 /**
     29  * This state machine handles Bluetooth Adapter State.
     30  * States:
     31  *      {@link OnState} : Bluetooth is on at this state
     32  *      {@link OffState}: Bluetooth is off at this state. This is the initial
     33  *      state.
     34  *      {@link PendingCommandState} : An enable / disable operation is pending.
     35  * TODO(BT): Add per process on state.
     36  */
     37 
     38 final class AdapterState extends StateMachine {
     39     private static final boolean DBG = true;
     40     private static final boolean VDBG = false;
     41     private static final String TAG = "BluetoothAdapterState";
     42 
     43     static final int USER_TURN_ON = 1;
     44     static final int STARTED=2;
     45     static final int ENABLED_READY = 3;
     46 
     47     static final int USER_TURN_OFF = 20;
     48     static final int BEGIN_DISABLE = 21;
     49     static final int ALL_DEVICES_DISCONNECTED = 22;
     50 
     51     static final int DISABLED = 24;
     52     static final int STOPPED=25;
     53 
     54     static final int START_TIMEOUT = 100;
     55     static final int ENABLE_TIMEOUT = 101;
     56     static final int DISABLE_TIMEOUT = 103;
     57     static final int STOP_TIMEOUT = 104;
     58     static final int SET_SCAN_MODE_TIMEOUT = 105;
     59 
     60     static final int USER_TURN_OFF_DELAY_MS=500;
     61 
     62     //TODO: tune me
     63     private static final int ENABLE_TIMEOUT_DELAY = 8000;
     64     private static final int DISABLE_TIMEOUT_DELAY = 8000;
     65     private static final int START_TIMEOUT_DELAY = 5000;
     66     private static final int STOP_TIMEOUT_DELAY = 5000;
     67     private static final int PROPERTY_OP_DELAY =2000;
     68     private AdapterService mAdapterService;
     69     private AdapterProperties mAdapterProperties;
     70     private PendingCommandState mPendingCommandState = new PendingCommandState();
     71     private OnState mOnState = new OnState();
     72     private OffState mOffState = new OffState();
     73 
     74     public boolean isTurningOn() {
     75         boolean isTurningOn=  mPendingCommandState.isTurningOn();
     76         if (VDBG) Log.d(TAG,"isTurningOn()=" + isTurningOn);
     77         return isTurningOn;
     78     }
     79 
     80     public boolean isTurningOff() {
     81         boolean isTurningOff= mPendingCommandState.isTurningOff();
     82         if (VDBG) Log.d(TAG,"isTurningOff()=" + isTurningOff);
     83         return isTurningOff;
     84     }
     85 
     86     private AdapterState(AdapterService service, AdapterProperties adapterProperties) {
     87         super("BluetoothAdapterState:");
     88         addState(mOnState);
     89         addState(mOffState);
     90         addState(mPendingCommandState);
     91         mAdapterService = service;
     92         mAdapterProperties = adapterProperties;
     93         setInitialState(mOffState);
     94     }
     95 
     96     public static AdapterState make(AdapterService service, AdapterProperties adapterProperties) {
     97         Log.d(TAG, "make");
     98         AdapterState as = new AdapterState(service, adapterProperties);
     99         as.start();
    100         return as;
    101     }
    102 
    103     public void doQuit() {
    104         quitNow();
    105     }
    106 
    107     public void cleanup() {
    108         if(mAdapterProperties != null)
    109             mAdapterProperties = null;
    110         if(mAdapterService != null)
    111             mAdapterService = null;
    112     }
    113 
    114     private class OffState extends State {
    115         @Override
    116         public void enter() {
    117             infoLog("Entering OffState");
    118         }
    119 
    120         @Override
    121         public boolean processMessage(Message msg) {
    122             AdapterService adapterService = mAdapterService;
    123             if (adapterService == null) {
    124                 Log.e(TAG,"receive message at OffState after cleanup:" +
    125                           msg.what);
    126                 return false;
    127             }
    128             switch(msg.what) {
    129                case USER_TURN_ON:
    130                    if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_ON");
    131                    notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
    132                    mPendingCommandState.setTurningOn(true);
    133                    transitionTo(mPendingCommandState);
    134                    sendMessageDelayed(START_TIMEOUT, START_TIMEOUT_DELAY);
    135                    adapterService.processStart();
    136                    break;
    137                case USER_TURN_OFF:
    138                    if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_OFF");
    139                    //TODO: Handle case of service started and stopped without enable
    140                    break;
    141                default:
    142                    if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=OFF, MESSAGE = " + msg.what );
    143                    return false;
    144             }
    145             return true;
    146         }
    147     }
    148 
    149     private class OnState extends State {
    150         @Override
    151         public void enter() {
    152             infoLog("Entering On State");
    153             AdapterService adapterService = mAdapterService;
    154             if (adapterService == null) {
    155                 Log.e(TAG,"enter OnState after cleanup");
    156                 return;
    157             }
    158             adapterService.autoConnect();
    159         }
    160 
    161         @Override
    162         public boolean processMessage(Message msg) {
    163             AdapterProperties adapterProperties = mAdapterProperties;
    164             if (adapterProperties == null) {
    165                 Log.e(TAG,"receive message at OnState after cleanup:" +
    166                           msg.what);
    167                 return false;
    168             }
    169 
    170             switch(msg.what) {
    171                case USER_TURN_OFF:
    172                    if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_OFF");
    173                    notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF);
    174                    mPendingCommandState.setTurningOff(true);
    175                    transitionTo(mPendingCommandState);
    176 
    177                    // Invoke onBluetoothDisable which shall trigger a
    178                    // setScanMode to SCAN_MODE_NONE
    179                    Message m = obtainMessage(SET_SCAN_MODE_TIMEOUT);
    180                    sendMessageDelayed(m, PROPERTY_OP_DELAY);
    181                    adapterProperties.onBluetoothDisable();
    182                    break;
    183 
    184                case USER_TURN_ON:
    185                    if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_ON");
    186                    Log.i(TAG,"Bluetooth already ON, ignoring USER_TURN_ON");
    187                    break;
    188                default:
    189                    if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=ON, MESSAGE = " + msg.what );
    190                    return false;
    191             }
    192             return true;
    193         }
    194     }
    195 
    196     private class PendingCommandState extends State {
    197         private boolean mIsTurningOn;
    198         private boolean mIsTurningOff;
    199 
    200         public void enter() {
    201             infoLog("Entering PendingCommandState State: isTurningOn()=" + isTurningOn() + ", isTurningOff()=" + isTurningOff());
    202         }
    203 
    204         public void setTurningOn(boolean isTurningOn) {
    205             mIsTurningOn = isTurningOn;
    206         }
    207 
    208         public boolean isTurningOn() {
    209             return mIsTurningOn;
    210         }
    211 
    212         public void setTurningOff(boolean isTurningOff) {
    213             mIsTurningOff = isTurningOff;
    214         }
    215 
    216         public boolean isTurningOff() {
    217             return mIsTurningOff;
    218         }
    219 
    220         @Override
    221         public boolean processMessage(Message msg) {
    222 
    223             boolean isTurningOn= isTurningOn();
    224             boolean isTurningOff = isTurningOff();
    225 
    226             AdapterService adapterService = mAdapterService;
    227             AdapterProperties adapterProperties = mAdapterProperties;
    228             if ((adapterService == null) || (adapterProperties == null)) {
    229                 Log.e(TAG,"receive message at Pending State after cleanup:" +
    230                           msg.what);
    231                 return false;
    232             }
    233 
    234             switch (msg.what) {
    235                 case USER_TURN_ON:
    236                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON"
    237                             + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    238                     if (isTurningOn) {
    239                         Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning on bluetooth... Ignoring USER_TURN_ON...");
    240                     } else {
    241                         Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_ON");
    242                         deferMessage(msg);
    243                     }
    244                     break;
    245                 case USER_TURN_OFF:
    246                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON"
    247                             + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    248                     if (isTurningOff) {
    249                         Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning off bluetooth... Ignoring USER_TURN_OFF...");
    250                     } else {
    251                         Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_OFF");
    252                         deferMessage(msg);
    253                     }
    254                     break;
    255                 case STARTED:   {
    256                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STARTED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    257                     //Remove start timeout
    258                     removeMessages(START_TIMEOUT);
    259 
    260                     //Enable
    261                     boolean ret = adapterService.enableNative();
    262                     if (!ret) {
    263                         Log.e(TAG, "Error while turning Bluetooth On");
    264                         notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
    265                         transitionTo(mOffState);
    266                     } else {
    267                         sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
    268                     }
    269                 }
    270                     break;
    271 
    272                 case ENABLED_READY:
    273                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_READY, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    274                     removeMessages(ENABLE_TIMEOUT);
    275                     adapterProperties.onBluetoothReady();
    276                     mPendingCommandState.setTurningOn(false);
    277                     transitionTo(mOnState);
    278                     notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
    279                     break;
    280 
    281                 case SET_SCAN_MODE_TIMEOUT:
    282                      Log.w(TAG,"Timeout will setting scan mode..Continuing with disable...");
    283                      //Fall through
    284                 case BEGIN_DISABLE: {
    285                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = BEGIN_DISABLE, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    286                     removeMessages(SET_SCAN_MODE_TIMEOUT);
    287                     sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
    288                     boolean ret = adapterService.disableNative();
    289                     if (!ret) {
    290                         removeMessages(DISABLE_TIMEOUT);
    291                         Log.e(TAG, "Error while turning Bluetooth Off");
    292                         //FIXME: what about post enable services
    293                         mPendingCommandState.setTurningOff(false);
    294                         notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
    295                     }
    296                 }
    297                     break;
    298                 case DISABLED:
    299                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    300                     if (isTurningOn) {
    301                         removeMessages(ENABLE_TIMEOUT);
    302                         errorLog("Error enabling Bluetooth - hardware init failed");
    303                         mPendingCommandState.setTurningOn(false);
    304                         transitionTo(mOffState);
    305                         adapterService.stopProfileServices();
    306                         notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
    307                         break;
    308                     }
    309                     removeMessages(DISABLE_TIMEOUT);
    310                     sendMessageDelayed(STOP_TIMEOUT, STOP_TIMEOUT_DELAY);
    311                     if (adapterService.stopProfileServices()) {
    312                         Log.d(TAG,"Stopping profile services that were post enabled");
    313                         break;
    314                     }
    315                     //Fall through if no services or services already stopped
    316                 case STOPPED:
    317                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOPPED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    318                     removeMessages(STOP_TIMEOUT);
    319                     setTurningOff(false);
    320                     transitionTo(mOffState);
    321                     notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
    322                     break;
    323                 case START_TIMEOUT:
    324                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = START_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    325                     errorLog("Error enabling Bluetooth");
    326                     mPendingCommandState.setTurningOn(false);
    327                     transitionTo(mOffState);
    328                     notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
    329                     break;
    330                 case ENABLE_TIMEOUT:
    331                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    332                     errorLog("Error enabling Bluetooth");
    333                     mPendingCommandState.setTurningOn(false);
    334                     transitionTo(mOffState);
    335                     notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
    336                     break;
    337                 case STOP_TIMEOUT:
    338                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOP_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    339                     errorLog("Error stopping Bluetooth profiles");
    340                     mPendingCommandState.setTurningOff(false);
    341                     transitionTo(mOffState);
    342                     break;
    343                 case DISABLE_TIMEOUT:
    344                     if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
    345                     errorLog("Error disabling Bluetooth");
    346                     mPendingCommandState.setTurningOff(false);
    347                     transitionTo(mOnState);
    348                     notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
    349                     break;
    350                 default:
    351                     if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=PENDING, MESSAGE = " + msg.what );
    352                     return false;
    353             }
    354             return true;
    355         }
    356     }
    357 
    358 
    359     private void notifyAdapterStateChange(int newState) {
    360         AdapterService adapterService = mAdapterService;
    361         AdapterProperties adapterProperties = mAdapterProperties;
    362         if ((adapterService == null) || (adapterProperties == null)) {
    363             Log.e(TAG,"notifyAdapterStateChange after cleanup:" + newState);
    364             return;
    365         }
    366 
    367         int oldState = adapterProperties.getState();
    368         adapterProperties.setState(newState);
    369         infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);
    370         adapterService.updateAdapterState(oldState, newState);
    371     }
    372 
    373     void stateChangeCallback(int status) {
    374         if (status == AbstractionLayer.BT_STATE_OFF) {
    375             sendMessage(DISABLED);
    376         } else if (status == AbstractionLayer.BT_STATE_ON) {
    377             // We should have got the property change for adapter and remote devices.
    378             sendMessage(ENABLED_READY);
    379         } else {
    380             errorLog("Incorrect status in stateChangeCallback");
    381         }
    382     }
    383 
    384     private void infoLog(String msg) {
    385         if (DBG) Log.i(TAG, msg);
    386     }
    387 
    388     private void errorLog(String msg) {
    389         Log.e(TAG, msg);
    390     }
    391 
    392 }
    393