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); }