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