/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.btservice; import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.content.Intent; import android.os.Message; import android.util.Log; import com.android.internal.util.State; import com.android.internal.util.StateMachine; /** * This state machine handles Bluetooth Adapter State. * States: * {@link OnState} : Bluetooth is on at this state * {@link OffState}: Bluetooth is off at this state. This is the initial * state. * {@link PendingCommandState} : An enable / disable operation is pending. * TODO(BT): Add per process on state. */ final class AdapterState extends StateMachine { private static final boolean DBG = true; private static final boolean VDBG = false; private static final String TAG = "BluetoothAdapterState"; static final int USER_TURN_ON = 1; static final int STARTED=2; static final int ENABLED_READY = 3; static final int USER_TURN_OFF = 20; static final int BEGIN_DISABLE = 21; static final int ALL_DEVICES_DISCONNECTED = 22; static final int DISABLED = 24; static final int STOPPED=25; static final int START_TIMEOUT = 100; static final int ENABLE_TIMEOUT = 101; static final int DISABLE_TIMEOUT = 103; static final int STOP_TIMEOUT = 104; static final int SET_SCAN_MODE_TIMEOUT = 105; static final int USER_TURN_OFF_DELAY_MS=500; //TODO: tune me private static final int ENABLE_TIMEOUT_DELAY = 8000; private static final int DISABLE_TIMEOUT_DELAY = 8000; private static final int START_TIMEOUT_DELAY = 5000; private static final int STOP_TIMEOUT_DELAY = 5000; private static final int PROPERTY_OP_DELAY =2000; private AdapterService mAdapterService; private AdapterProperties mAdapterProperties; private PendingCommandState mPendingCommandState = new PendingCommandState(); private OnState mOnState = new OnState(); private OffState mOffState = new OffState(); public boolean isTurningOn() { boolean isTurningOn= mPendingCommandState.isTurningOn(); if (VDBG) Log.d(TAG,"isTurningOn()=" + isTurningOn); return isTurningOn; } public boolean isTurningOff() { boolean isTurningOff= mPendingCommandState.isTurningOff(); if (VDBG) Log.d(TAG,"isTurningOff()=" + isTurningOff); return isTurningOff; } private AdapterState(AdapterService service, AdapterProperties adapterProperties) { super("BluetoothAdapterState:"); addState(mOnState); addState(mOffState); addState(mPendingCommandState); mAdapterService = service; mAdapterProperties = adapterProperties; setInitialState(mOffState); } public static AdapterState make(AdapterService service, AdapterProperties adapterProperties) { Log.d(TAG, "make"); AdapterState as = new AdapterState(service, adapterProperties); as.start(); return as; } public void doQuit() { quitNow(); } public void cleanup() { if(mAdapterProperties != null) mAdapterProperties = null; if(mAdapterService != null) mAdapterService = null; } private class OffState extends State { @Override public void enter() { infoLog("Entering OffState"); } @Override public boolean processMessage(Message msg) { AdapterService adapterService = mAdapterService; if (adapterService == null) { Log.e(TAG,"receive message at OffState after cleanup:" + msg.what); return false; } switch(msg.what) { case USER_TURN_ON: if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_ON"); notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON); mPendingCommandState.setTurningOn(true); transitionTo(mPendingCommandState); sendMessageDelayed(START_TIMEOUT, START_TIMEOUT_DELAY); adapterService.processStart(); break; case USER_TURN_OFF: if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_OFF"); //TODO: Handle case of service started and stopped without enable break; default: if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=OFF, MESSAGE = " + msg.what ); return false; } return true; } } private class OnState extends State { @Override public void enter() { infoLog("Entering On State"); AdapterService adapterService = mAdapterService; if (adapterService == null) { Log.e(TAG,"enter OnState after cleanup"); return; } adapterService.autoConnect(); } @Override public boolean processMessage(Message msg) { AdapterProperties adapterProperties = mAdapterProperties; if (adapterProperties == null) { Log.e(TAG,"receive message at OnState after cleanup:" + msg.what); return false; } switch(msg.what) { case USER_TURN_OFF: if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_OFF"); notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF); mPendingCommandState.setTurningOff(true); transitionTo(mPendingCommandState); // Invoke onBluetoothDisable which shall trigger a // setScanMode to SCAN_MODE_NONE Message m = obtainMessage(SET_SCAN_MODE_TIMEOUT); sendMessageDelayed(m, PROPERTY_OP_DELAY); adapterProperties.onBluetoothDisable(); break; case USER_TURN_ON: if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_ON"); Log.i(TAG,"Bluetooth already ON, ignoring USER_TURN_ON"); break; default: if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=ON, MESSAGE = " + msg.what ); return false; } return true; } } private class PendingCommandState extends State { private boolean mIsTurningOn; private boolean mIsTurningOff; public void enter() { infoLog("Entering PendingCommandState State: isTurningOn()=" + isTurningOn() + ", isTurningOff()=" + isTurningOff()); } public void setTurningOn(boolean isTurningOn) { mIsTurningOn = isTurningOn; } public boolean isTurningOn() { return mIsTurningOn; } public void setTurningOff(boolean isTurningOff) { mIsTurningOff = isTurningOff; } public boolean isTurningOff() { return mIsTurningOff; } @Override public boolean processMessage(Message msg) { boolean isTurningOn= isTurningOn(); boolean isTurningOff = isTurningOff(); AdapterService adapterService = mAdapterService; AdapterProperties adapterProperties = mAdapterProperties; if ((adapterService == null) || (adapterProperties == null)) { Log.e(TAG,"receive message at Pending State after cleanup:" + msg.what); return false; } switch (msg.what) { case USER_TURN_ON: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON" + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); if (isTurningOn) { Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning on bluetooth... Ignoring USER_TURN_ON..."); } else { Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_ON"); deferMessage(msg); } break; case USER_TURN_OFF: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON" + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); if (isTurningOff) { Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning off bluetooth... Ignoring USER_TURN_OFF..."); } else { Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_OFF"); deferMessage(msg); } break; case STARTED: { if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STARTED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); //Remove start timeout removeMessages(START_TIMEOUT); //Enable boolean ret = adapterService.enableNative(); if (!ret) { Log.e(TAG, "Error while turning Bluetooth On"); notifyAdapterStateChange(BluetoothAdapter.STATE_OFF); transitionTo(mOffState); } else { sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY); } } break; case ENABLED_READY: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_READY, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); removeMessages(ENABLE_TIMEOUT); adapterProperties.onBluetoothReady(); mPendingCommandState.setTurningOn(false); transitionTo(mOnState); notifyAdapterStateChange(BluetoothAdapter.STATE_ON); break; case SET_SCAN_MODE_TIMEOUT: Log.w(TAG,"Timeout will setting scan mode..Continuing with disable..."); //Fall through case BEGIN_DISABLE: { if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = BEGIN_DISABLE, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); removeMessages(SET_SCAN_MODE_TIMEOUT); sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY); boolean ret = adapterService.disableNative(); if (!ret) { removeMessages(DISABLE_TIMEOUT); Log.e(TAG, "Error while turning Bluetooth Off"); //FIXME: what about post enable services mPendingCommandState.setTurningOff(false); notifyAdapterStateChange(BluetoothAdapter.STATE_ON); } } break; case DISABLED: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); if (isTurningOn) { removeMessages(ENABLE_TIMEOUT); errorLog("Error enabling Bluetooth - hardware init failed"); mPendingCommandState.setTurningOn(false); transitionTo(mOffState); adapterService.stopProfileServices(); notifyAdapterStateChange(BluetoothAdapter.STATE_OFF); break; } removeMessages(DISABLE_TIMEOUT); sendMessageDelayed(STOP_TIMEOUT, STOP_TIMEOUT_DELAY); if (adapterService.stopProfileServices()) { Log.d(TAG,"Stopping profile services that were post enabled"); break; } //Fall through if no services or services already stopped case STOPPED: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOPPED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); removeMessages(STOP_TIMEOUT); setTurningOff(false); transitionTo(mOffState); notifyAdapterStateChange(BluetoothAdapter.STATE_OFF); break; case START_TIMEOUT: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = START_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); errorLog("Error enabling Bluetooth"); mPendingCommandState.setTurningOn(false); transitionTo(mOffState); notifyAdapterStateChange(BluetoothAdapter.STATE_OFF); break; case ENABLE_TIMEOUT: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); errorLog("Error enabling Bluetooth"); mPendingCommandState.setTurningOn(false); transitionTo(mOffState); notifyAdapterStateChange(BluetoothAdapter.STATE_OFF); break; case STOP_TIMEOUT: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOP_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); errorLog("Error stopping Bluetooth profiles"); mPendingCommandState.setTurningOff(false); transitionTo(mOffState); break; case DISABLE_TIMEOUT: if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff); errorLog("Error disabling Bluetooth"); mPendingCommandState.setTurningOff(false); transitionTo(mOnState); notifyAdapterStateChange(BluetoothAdapter.STATE_ON); break; default: if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=PENDING, MESSAGE = " + msg.what ); return false; } return true; } } private void notifyAdapterStateChange(int newState) { AdapterService adapterService = mAdapterService; AdapterProperties adapterProperties = mAdapterProperties; if ((adapterService == null) || (adapterProperties == null)) { Log.e(TAG,"notifyAdapterStateChange after cleanup:" + newState); return; } int oldState = adapterProperties.getState(); adapterProperties.setState(newState); infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState); adapterService.updateAdapterState(oldState, newState); } void stateChangeCallback(int status) { if (status == AbstractionLayer.BT_STATE_OFF) { sendMessage(DISABLED); } else if (status == AbstractionLayer.BT_STATE_ON) { // We should have got the property change for adapter and remote devices. sendMessage(ENABLED_READY); } else { errorLog("Incorrect status in stateChangeCallback"); } } private void infoLog(String msg) { if (DBG) Log.i(TAG, msg); } private void errorLog(String msg) { Log.e(TAG, msg); } }