blob: b47d3242bc1828cf0eb141bca67a5ffab5e93ac8 [file] [log] [blame]
/*
* 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.os.UserManager;
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 = true;
private static final String TAG = "BluetoothAdapterState";
static final int BLE_TURN_ON = 0;
static final int USER_TURN_ON = 1;
static final int BREDR_STARTED=2;
static final int ENABLED_READY = 3;
static final int BLE_STARTED=4;
static final int USER_TURN_OFF = 20;
static final int BEGIN_DISABLE = 21;
static final int ALL_DEVICES_DISCONNECTED = 22;
static final int BLE_TURN_OFF = 23;
static final int DISABLED = 24;
static final int BLE_STOPPED=25;
static final int BREDR_STOPPED = 26;
static final int BREDR_START_TIMEOUT = 100;
static final int ENABLE_TIMEOUT = 101;
static final int DISABLE_TIMEOUT = 103;
static final int BLE_STOP_TIMEOUT = 104;
static final int SET_SCAN_MODE_TIMEOUT = 105;
static final int BLE_START_TIMEOUT = 106;
static final int BREDR_STOP_TIMEOUT = 107;
static final int USER_TURN_OFF_DELAY_MS=500;
//TODO: tune me
private static final int ENABLE_TIMEOUT_DELAY = 12000;
private static final int DISABLE_TIMEOUT_DELAY = 8000;
private static final int BREDR_START_TIMEOUT_DELAY = 4000;
//BLE_START_TIMEOUT can happen quickly as it just a start gattservice
private static final int BLE_START_TIMEOUT_DELAY = 2000; //To start GattService
private static final int BLE_STOP_TIMEOUT_DELAY = 2000;
//BREDR_STOP_TIMEOUT can < STOP_TIMEOUT
private static final int BREDR_STOP_TIMEOUT_DELAY = 4000;
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();
private BleOnState mBleOnState = new BleOnState();
public boolean isTurningOn() {
boolean isTurningOn= mPendingCommandState.isTurningOn();
verboseLog("isTurningOn()=" + isTurningOn);
return isTurningOn;
}
public boolean isBleTurningOn() {
boolean isBleTurningOn= mPendingCommandState.isBleTurningOn();
verboseLog("isBleTurningOn()=" + isBleTurningOn);
return isBleTurningOn;
}
public boolean isBleTurningOff() {
boolean isBleTurningOff = mPendingCommandState.isBleTurningOff();
verboseLog("isBleTurningOff()=" + isBleTurningOff);
return isBleTurningOff;
}
public boolean isTurningOff() {
boolean isTurningOff= mPendingCommandState.isTurningOff();
verboseLog("isTurningOff()=" + isTurningOff);
return isTurningOff;
}
private AdapterState(AdapterService service, AdapterProperties adapterProperties) {
super("BluetoothAdapterState:");
addState(mOnState);
addState(mBleOnState);
addState(mOffState);
addState(mPendingCommandState);
mAdapterService = service;
mAdapterProperties = adapterProperties;
setInitialState(mOffState);
}
public static AdapterState make(AdapterService service, AdapterProperties adapterProperties) {
Log.d(TAG, "make() - Creating AdapterState");
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) {
errorLog("Received message in OffState after cleanup: " + msg.what);
return false;
}
debugLog("Current state: OFF, message: " + msg.what);
switch(msg.what) {
case BLE_TURN_ON:
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON);
mPendingCommandState.setBleTurningOn(true);
transitionTo(mPendingCommandState);
sendMessageDelayed(BLE_START_TIMEOUT, BLE_START_TIMEOUT_DELAY);
adapterService.BleOnProcessStart();
break;
case USER_TURN_OFF:
//TODO: Handle case of service started and stopped without enable
break;
default:
return false;
}
return true;
}
}
private class BleOnState extends State {
@Override
public void enter() {
infoLog("Entering BleOnState");
}
@Override
public boolean processMessage(Message msg) {
AdapterService adapterService = mAdapterService;
AdapterProperties adapterProperties = mAdapterProperties;
if ((adapterService == null) || (adapterProperties == null)) {
errorLog("Received message in BleOnState after cleanup: " + msg.what);
return false;
}
debugLog("Current state: BLE ON, message: " + msg.what);
switch(msg.what) {
case USER_TURN_ON:
notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
mPendingCommandState.setTurningOn(true);
transitionTo(mPendingCommandState);
sendMessageDelayed(BREDR_START_TIMEOUT, BREDR_START_TIMEOUT_DELAY);
adapterService.startCoreServices();
break;
case USER_TURN_OFF:
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF);
mPendingCommandState.setBleTurningOff(true);
adapterProperties.onBleDisable();
transitionTo(mPendingCommandState);
sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
boolean ret = adapterService.disableNative();
if (!ret) {
removeMessages(DISABLE_TIMEOUT);
errorLog("Error while calling disableNative");
//FIXME: what about post enable services
mPendingCommandState.setBleTurningOff(false);
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
}
break;
default:
return false;
}
return true;
}
}
private class OnState extends State {
@Override
public void enter() {
infoLog("Entering OnState");
AdapterService adapterService = mAdapterService;
if (adapterService == null) {
errorLog("Entered OnState after cleanup");
return;
}
adapterService.autoConnect();
}
@Override
public boolean processMessage(Message msg) {
AdapterProperties adapterProperties = mAdapterProperties;
if (adapterProperties == null) {
errorLog("Received message in OnState after cleanup: " + msg.what);
return false;
}
debugLog("Current state: ON, message: " + msg.what);
switch(msg.what) {
case BLE_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:
break;
default:
return false;
}
return true;
}
}
private class PendingCommandState extends State {
private boolean mIsTurningOn;
private boolean mIsTurningOff;
private boolean mIsBleTurningOn;
private boolean mIsBleTurningOff;
public void enter() {
infoLog("Entering PendingCommandState");
}
public void setTurningOn(boolean isTurningOn) {
mIsTurningOn = isTurningOn;
}
public boolean isTurningOn() {
return mIsTurningOn;
}
public void setTurningOff(boolean isTurningOff) {
mIsTurningOff = isTurningOff;
}
public boolean isTurningOff() {
return mIsTurningOff;
}
public void setBleTurningOn(boolean isBleTurningOn) {
mIsBleTurningOn = isBleTurningOn;
}
public boolean isBleTurningOn() {
return mIsBleTurningOn;
}
public void setBleTurningOff(boolean isBleTurningOff) {
mIsBleTurningOff = isBleTurningOff;
}
public boolean isBleTurningOff() {
return mIsBleTurningOff;
}
@Override
public boolean processMessage(Message msg) {
boolean isTurningOn= isTurningOn();
boolean isTurningOff = isTurningOff();
boolean isBleTurningOn = isBleTurningOn();
boolean isBleTurningOff = isBleTurningOff();
AdapterService adapterService = mAdapterService;
AdapterProperties adapterProperties = mAdapterProperties;
if ((adapterService == null) || (adapterProperties == null)) {
errorLog("Received message in PendingCommandState after cleanup: " + msg.what);
return false;
}
debugLog("Current state: PENDING_COMMAND, message: " + msg.what);
switch (msg.what) {
case USER_TURN_ON:
if (isBleTurningOff || isTurningOff) { //TODO:do we need to send it after ble turn off also??
infoLog("Deferring USER_TURN_ON request...");
deferMessage(msg);
}
break;
case USER_TURN_OFF:
if (isTurningOn || isBleTurningOn) {
infoLog("Deferring USER_TURN_OFF request...");
deferMessage(msg);
}
break;
case BLE_TURN_ON:
if (isTurningOff || isBleTurningOff) {
infoLog("Deferring BLE_TURN_ON request...");
deferMessage(msg);
}
break;
case BLE_TURN_OFF:
if (isTurningOn || isBleTurningOn) {
infoLog("Deferring BLE_TURN_OFF request...");
deferMessage(msg);
}
break;
case BLE_STARTED:
//Remove start timeout
removeMessages(BLE_START_TIMEOUT);
//Enable
boolean isGuest = UserManager.get(mAdapterService).isGuestUser();
if (!adapterService.enableNative(isGuest)) {
errorLog("Error while turning Bluetooth on");
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
transitionTo(mOffState);
} else {
sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
}
break;
case BREDR_STARTED:
//Remove start timeout
removeMessages(BREDR_START_TIMEOUT);
adapterProperties.onBluetoothReady();
mPendingCommandState.setTurningOn(false);
transitionTo(mOnState);
notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
break;
case ENABLED_READY:
removeMessages(ENABLE_TIMEOUT);
mPendingCommandState.setBleTurningOn(false);
transitionTo(mBleOnState);
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
break;
case SET_SCAN_MODE_TIMEOUT:
warningLog("Timeout while setting scan mode. Continuing with disable...");
//Fall through
case BEGIN_DISABLE:
removeMessages(SET_SCAN_MODE_TIMEOUT);
sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
adapterService.stopProfileServices();
break;
case DISABLED:
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(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
if (adapterService.stopGattProfileService()) {
debugLog("Stopping Gatt profile services that were post enabled");
break;
}
//Fall through if no services or services already stopped
case BLE_STOPPED:
removeMessages(BLE_STOP_TIMEOUT);
setBleTurningOff(false);
transitionTo(mOffState);
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
break;
case BREDR_STOPPED:
removeMessages(BREDR_STOP_TIMEOUT);
setTurningOff(false);
transitionTo(mBleOnState);
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
break;
case BLE_START_TIMEOUT:
errorLog("Error enabling Bluetooth (BLE start timeout)");
mPendingCommandState.setBleTurningOn(false);
transitionTo(mOffState);
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
break;
case BREDR_START_TIMEOUT:
errorLog("Error enabling Bluetooth (start timeout)");
mPendingCommandState.setTurningOn(false);
transitionTo(mBleOnState);
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
break;
case ENABLE_TIMEOUT:
errorLog("Error enabling Bluetooth (enable timeout)");
mPendingCommandState.setBleTurningOn(false);
transitionTo(mOffState);
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
break;
case BREDR_STOP_TIMEOUT:
errorLog("Error stopping Bluetooth profiles (stop timeout)");
mPendingCommandState.setTurningOff(false);
transitionTo(mBleOnState);
notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
break;
case BLE_STOP_TIMEOUT:
errorLog("Error stopping Bluetooth profiles (BLE stop timeout)");
mPendingCommandState.setTurningOff(false);
transitionTo(mOffState);
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
break;
case DISABLE_TIMEOUT:
errorLog("Error disabling Bluetooth (disable timeout)");
if (isTurningOn)
mPendingCommandState.setTurningOn(false);
adapterService.stopProfileServices();
adapterService.stopGattProfileService();
mPendingCommandState.setTurningOff(false);
setBleTurningOff(false);
transitionTo(mOffState);
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
break;
default:
return false;
}
return true;
}
}
private void notifyAdapterStateChange(int newState) {
AdapterService adapterService = mAdapterService;
AdapterProperties adapterProperties = mAdapterProperties;
if ((adapterService == null) || (adapterProperties == null)) {
errorLog("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 debugLog(String msg) {
if (DBG) Log.d(TAG, msg);
}
private void warningLog(String msg) {
if (DBG) Log.d(TAG, msg);
}
private void verboseLog(String msg) {
if (VDBG) Log.v(TAG, msg);
}
private void errorLog(String msg) {
Log.e(TAG, msg);
}
}