blob: 5f9c30bf412381115f97b3358e07b356f3f58a71 [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.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);
}
}