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