package com.android.settings.widget;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* The state machine for Wifi and Bluetooth toggling, tracking reality
* versus the user's intent.
*
* This is necessary because reality moves relatively slowly (turning on
* & off radio drivers), compared to user's expectations.
*/
public abstract class StateTracker {
// Is the state in the process of changing?
private boolean mInTransition = false;
private Boolean mActualState = null; // initially not set
private Boolean mIntendedState = null; // initially not set
// Did a toggle request arrive while a state update was
// already in-flight? If so, the mIntendedState needs to be
// requested when the other one is done, unless we happened to
// arrive at that state already.
private boolean mDeferredStateChangeRequestNeeded = false;
/**
* User pressed a button to change the state. Something should
* immediately appear to the user afterwards, even if we effectively do
* nothing. Their press must be heard.
*/
public final void toggleState(Context context) {
int currentState = getTriState(context);
boolean newState = false;
switch (currentState) {
case SettingsAppWidgetProvider.STATE_ENABLED:
newState = false;
break;
case SettingsAppWidgetProvider.STATE_DISABLED:
newState = true;
break;
case SettingsAppWidgetProvider.STATE_INTERMEDIATE:
if (mIntendedState != null) {
newState = !mIntendedState;
}
break;
}
mIntendedState = newState;
if (mInTransition) {
// We don't send off a transition request if we're
// already transitioning. Makes our state tracking
// easier, and is probably nicer on lower levels.
// (even though they should be able to take it...)
mDeferredStateChangeRequestNeeded = true;
} else {
mInTransition = true;
requestStateChange(context, newState);
}
}
/**
* Update internal state from a broadcast state change.
*/
public abstract void onActualStateChange(Context context, Intent intent);
/**
* Sets the value that we're now in. To be called from
* onActualStateChange.
*
* @param newState
* one of STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON,
* STATE_TURNING_OFF, STATE_UNKNOWN
*/
protected final void setCurrentState(Context context, int newState) {
final boolean wasInTransition = mInTransition;
switch (newState) {
case SettingsAppWidgetProvider.STATE_DISABLED:
mInTransition = false;
mActualState = false;
break;
case SettingsAppWidgetProvider.STATE_ENABLED:
mInTransition = false;
mActualState = true;
break;
case SettingsAppWidgetProvider.STATE_TURNING_ON:
mInTransition = true;
mActualState = false;
break;
case SettingsAppWidgetProvider.STATE_TURNING_OFF:
mInTransition = true;
mActualState = true;
break;
}
if (wasInTransition && !mInTransition) {
if (mDeferredStateChangeRequestNeeded) {
Log.v(SettingsAppWidgetProvider.TAG, "processing deferred state change");
if (mActualState != null && mIntendedState != null
&& mIntendedState.equals(mActualState)) {
Log
.v(SettingsAppWidgetProvider.TAG,
"... but intended state matches, so no changes.");
} else if (mIntendedState != null) {
mInTransition = true;
requestStateChange(context, mIntendedState);
}
mDeferredStateChangeRequestNeeded = false;
}
}
}
/**
* If we're in a transition mode, this returns true if we're
* transitioning towards being enabled.
*/
public final boolean isTurningOn() {
return mIntendedState != null && mIntendedState;
}
/**
* Returns simplified 3-state value from underlying 5-state.
*
* @param context
* @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE
*/
public final int getTriState(Context context) {
if (mInTransition) {
// If we know we just got a toggle request recently
// (which set mInTransition), don't even ask the
// underlying interface for its state. We know we're
// changing. This avoids blocking the UI thread
// during UI refresh post-toggle if the underlying
// service state accessor has coarse locking on its
// state (to be fixed separately).
return SettingsAppWidgetProvider.STATE_INTERMEDIATE;
}
switch (getActualState(context)) {
case SettingsAppWidgetProvider.STATE_DISABLED:
return SettingsAppWidgetProvider.STATE_DISABLED;
case SettingsAppWidgetProvider.STATE_ENABLED:
return SettingsAppWidgetProvider.STATE_ENABLED;
default:
return SettingsAppWidgetProvider.STATE_INTERMEDIATE;
}
}
/**
* Gets underlying actual state.
*
* @param context
* @return STATE_ENABLED, STATE_DISABLED, STATE_ENABLING,
* STATE_DISABLING, or or STATE_UNKNOWN.
*/
public abstract int getActualState(Context context);
/**
* Actually make the desired change to the underlying radio API.
*/
protected abstract void requestStateChange(Context context,
boolean desiredState);
}