| /* |
| * Copyright (C) 2006 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 android.os; |
| |
| import android.util.Log; |
| import android.util.LogPrinter; |
| |
| /** |
| * {@hide} |
| * |
| * Implement a state machine where each state is an object, |
| * HandlerState. Each HandlerState must implement processMessage |
| * and optionally enter/exit. When a state machine is created |
| * the initial state must be set. When messages are sent to |
| * a state machine the current state's processMessage method is |
| * invoked. If this is the first message for this state the |
| * enter method is called prior to processMessage and when |
| * transtionTo is invoked the state's exit method will be |
| * called after returning from processMessage. |
| * |
| * If a message should be handled in a different state the |
| * processMessage method may call deferMessage. This causes |
| * the message to be saved on a list until transitioning |
| * to a new state, at which time all of the deferred messages |
| * will be put on the front of the state machines queue and |
| * processed by the new current state's processMessage |
| * method. |
| * |
| * Below is an example state machine with two state's, S1 and S2. |
| * The initial state is S1 which defers all messages and only |
| * transition to S2 when message.what == TEST_WHAT_2. State S2 |
| * will process each messages until it receives TEST_WHAT_2 |
| * where it will transition back to S1: |
| <code> |
| class StateMachine1 extends HandlerStateMachine { |
| private static final int TEST_WHAT_1 = 1; |
| private static final int TEST_WHAT_2 = 2; |
| |
| StateMachine1(String name) { |
| super(name); |
| setInitialState(mS1); |
| } |
| |
| class S1 extends HandlerState { |
| @Override public void enter(Message message) { |
| } |
| |
| @Override public void processMessage(Message message) { |
| deferMessage(message); |
| if (message.what == TEST_WHAT_2) { |
| transitionTo(mS2); |
| } |
| } |
| |
| @Override public void exit(Message message) { |
| } |
| } |
| |
| class S2 extends HandlerState { |
| @Override public void processMessage(Message message) { |
| // Do some processing |
| if (message.what == TEST_WHAT_2) { |
| transtionTo(mS1); |
| } |
| } |
| } |
| |
| private S1 mS1 = new S1(); |
| private S2 mS2 = new S2(); |
| } |
| </code> |
| */ |
| public class HandlerStateMachine { |
| |
| private boolean mDbg = false; |
| private static final String TAG = "HandlerStateMachine"; |
| private String mName; |
| private SmHandler mHandler; |
| private HandlerThread mHandlerThread; |
| |
| /** |
| * Handle messages sent to the state machine by calling |
| * the current state's processMessage. It also handles |
| * the enter/exit calls and placing any deferred messages |
| * back onto the queue when transitioning to a new state. |
| */ |
| class SmHandler extends Handler { |
| |
| SmHandler(Looper looper) { |
| super(looper); |
| } |
| |
| /** |
| * This will dispatch the message to the |
| * current state's processMessage. |
| */ |
| @Override |
| final public void handleMessage(Message msg) { |
| if (mDbg) Log.d(TAG, "SmHandler.handleMessage E"); |
| if (mDestState != null) { |
| if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter"); |
| mCurrentState = mDestState; |
| mDestState = null; |
| mCurrentState.enter(msg); |
| } |
| if (mCurrentState != null) { |
| if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage"); |
| mCurrentState.processMessage(msg); |
| } else { |
| /* Strange no state to execute */ |
| Log.e(TAG, "handleMessage: no current state, did you call setInitialState"); |
| } |
| |
| if (mDestState != null) { |
| if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit"); |
| mCurrentState.exit(msg); |
| |
| /** |
| * Place the messages from the deferred queue:t |
| * on to the Handler's message queue in the |
| * same order that they originally arrived. |
| * |
| * We set cur.when = 0 to circumvent the check |
| * that this message has already been sent. |
| */ |
| while (mDeferredMessages != null) { |
| Message cur = mDeferredMessages; |
| mDeferredMessages = mDeferredMessages.next; |
| cur.when = 0; |
| if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what=" |
| + cur.what + " target=" + cur.target); |
| sendMessageAtFrontOfQueue(cur); |
| } |
| if (mDbg) Log.d(TAG, "SmHandler.handleMessage X"); |
| } |
| } |
| |
| public HandlerState mCurrentState; |
| public HandlerState mDestState; |
| public Message mDeferredMessages; |
| } |
| |
| /** |
| * Create an active StateMachine, one that has a |
| * dedicated thread/looper/queue. |
| */ |
| public HandlerStateMachine(String name) { |
| mName = name; |
| mHandlerThread = new HandlerThread(name); |
| mHandlerThread.start(); |
| mHandler = new SmHandler(mHandlerThread.getLooper()); |
| } |
| |
| /** |
| * Get a message and set Message.target = this. |
| */ |
| public final Message obtainMessage() |
| { |
| Message msg = Message.obtain(mHandler); |
| if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target); |
| return msg; |
| } |
| |
| /** |
| * Get a message and set Message.target = this and |
| * Message.what = what. |
| */ |
| public final Message obtainMessage(int what) { |
| Message msg = Message.obtain(mHandler, what); |
| if (mDbg) { |
| Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what + |
| " target=" + msg.target); |
| } |
| return msg; |
| } |
| |
| /** |
| * Enqueue a message to this state machine. |
| */ |
| public final void sendMessage(Message msg) { |
| if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what); |
| mHandler.sendMessage(msg); |
| } |
| |
| /** |
| * Enqueue a message to this state machine after a delay. |
| */ |
| public final void sendMessageDelayed(Message msg, long delayMillis) { |
| if (mDbg) { |
| Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what=" |
| + msg.what + " delay=" + delayMillis); |
| } |
| mHandler.sendMessageDelayed(msg, delayMillis); |
| } |
| |
| /** |
| * Set the initial state. This must be invoked before |
| * and messages are sent to the state machine. |
| */ |
| public void setInitialState(HandlerState initialState) { |
| if (mDbg) { |
| Log.d(TAG, "StateMachine.setInitialState EX initialState" |
| + initialState.getClass().getName()); |
| } |
| mHandler.mDestState = initialState; |
| } |
| |
| /** |
| * transition to destination state. Upon returning |
| * from processMessage the current state's exit will |
| * be executed and upon the next message arriving |
| * destState.enter will be invoked. |
| */ |
| final public void transitionTo(HandlerState destState) { |
| if (mDbg) { |
| Log.d(TAG, "StateMachine.transitionTo EX destState" |
| + destState.getClass().getName()); |
| } |
| mHandler.mDestState = destState; |
| } |
| |
| /** |
| * Defer this message until next state transition. |
| * Upon transitioning all deferred messages will be |
| * placed on the queue and reprocessed in the original |
| * order. (i.e. The next state the oldest messages will |
| * be processed first) |
| */ |
| final public void deferMessage(Message msg) { |
| if (mDbg) { |
| Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages=" |
| + mHandler.mDeferredMessages); |
| } |
| |
| /* Copy the "msg" to "newMsg" as "msg" will be recycled */ |
| Message newMsg = obtainMessage(); |
| newMsg.copyFrom(msg); |
| |
| /* Place on front of queue */ |
| newMsg.next = mHandler.mDeferredMessages; |
| mHandler.mDeferredMessages = newMsg; |
| } |
| |
| /** |
| * @return the name |
| */ |
| public String getName() { |
| return mName; |
| } |
| |
| /** |
| * @return Handler |
| */ |
| public Handler getHandler() { |
| return mHandler; |
| } |
| |
| /** |
| * @return if debugging is enabled |
| */ |
| public boolean isDbg() { |
| return mDbg; |
| } |
| |
| /** |
| * Set debug enable/disabled. |
| */ |
| public void setDbg(boolean dbg) { |
| mDbg = dbg; |
| if (mDbg) { |
| mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG)); |
| } else { |
| mHandlerThread.getLooper().setMessageLogging(null); |
| } |
| } |
| } |