Implement a HierarchicalStateMachine
A hierarchical state machine is a state machine which processes messages and can have states arranged hierarchically. Each state in the state machine may have a single parent state and if a child state is unable to handle a message it may have the message processed by its parent. Change-Id: I0a56959ece8f89e4f9122dc8044120b82d517bbb
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public abstract class HandlerState {
|
||||
public HandlerState() {
|
||||
}
|
||||
|
||||
public void enter(Message message) {
|
||||
}
|
||||
|
||||
public abstract void processMessage(Message message);
|
||||
|
||||
public void exit(Message message) {
|
||||
}
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
&#064;Override public void enter(Message message) {
|
||||
}
|
||||
|
||||
&#064;Override public void processMessage(Message message) {
|
||||
deferMessage(message);
|
||||
if (message.what == TEST_WHAT_2) {
|
||||
transitionTo(mS2);
|
||||
}
|
||||
}
|
||||
|
||||
&#064;Override public void exit(Message message) {
|
||||
}
|
||||
}
|
||||
|
||||
class S2 extends HandlerState {
|
||||
&#064;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);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
core/java/com/android/internal/util/HierarchicalState.java
Normal file
75
core/java/com/android/internal/util/HierarchicalState.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (C) 2009 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.internal.util;
|
||||
|
||||
import android.os.Message;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*
|
||||
* The abstract class for implementing states in a
|
||||
* HierarchicalStateMachine and HandlerStateMachine.
|
||||
*/
|
||||
public abstract class HierarchicalState {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
protected HierarchicalState() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a state is entered.
|
||||
*/
|
||||
protected void enter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message is to be processed by the
|
||||
* state machine.
|
||||
*
|
||||
* This routine is never reentered thus no synchronization
|
||||
* is needed as only one processMessage method will ever be
|
||||
* executing within a state machine at any given time. This
|
||||
* does mean that processing by this routine must be completed
|
||||
* as expeditiously as possible as no subsequent messages will
|
||||
* be processed until this routine returns.
|
||||
*
|
||||
* @param msg to process
|
||||
* @return true if processing has completed and false
|
||||
* if the parent state's processMessage should
|
||||
* be invoked.
|
||||
*/
|
||||
abstract protected boolean processMessage(Message msg);
|
||||
|
||||
/**
|
||||
* Called when a state is exited.
|
||||
*/
|
||||
protected void exit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of state, but default returns the states
|
||||
* class name. An instance name would be better but requiring
|
||||
* it seems unnecessary.
|
||||
*/
|
||||
public String getName() {
|
||||
String name = getClass().getName();
|
||||
int lastDollar = name.lastIndexOf('$');
|
||||
return name.substring(lastDollar + 1);
|
||||
}
|
||||
}
|
||||
1164
core/java/com/android/internal/util/HierarchicalStateMachine.java
Normal file
1164
core/java/com/android/internal/util/HierarchicalStateMachine.java
Normal file
File diff suppressed because it is too large
Load Diff
198
core/java/com/android/internal/util/ProcessedMessages.java
Normal file
198
core/java/com/android/internal/util/ProcessedMessages.java
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright (C) 2009 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.internal.util;
|
||||
|
||||
import android.os.Message;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*
|
||||
* A list of messages recently processed by the state machine.
|
||||
*
|
||||
* The class maintains a list of messages that have been most
|
||||
* recently processed. The list is finite and may be set in the
|
||||
* constructor or by calling setSize. The public interface also
|
||||
* includes size which returns the number of recent messages,
|
||||
* count which is the number of message processed since the
|
||||
* the last setSize, get which returns a processed message and
|
||||
* add which adds a processed messaged.
|
||||
*/
|
||||
public class ProcessedMessages {
|
||||
|
||||
public static final int DEFAULT_SIZE = 20;
|
||||
|
||||
/**
|
||||
* The information maintained for a processed message.
|
||||
*/
|
||||
public class Info {
|
||||
private int what;
|
||||
private HierarchicalState state;
|
||||
private HierarchicalState orgState;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param message
|
||||
* @param state that handled the message
|
||||
* @param orgState is the first state the received the message but
|
||||
* did not processes the message.
|
||||
*/
|
||||
Info(Message message, HierarchicalState state, HierarchicalState orgState) {
|
||||
update(message, state, orgState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the information in the record.
|
||||
* @param state that handled the message
|
||||
* @param orgState is the first state the received the message but
|
||||
* did not processes the message.
|
||||
*/
|
||||
public void update(Message message, HierarchicalState state, HierarchicalState orgState) {
|
||||
this.what = message.what;
|
||||
this.state = state;
|
||||
this.orgState = orgState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the command that was executing
|
||||
*/
|
||||
public int getWhat() {
|
||||
return what;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the state that handled this message
|
||||
*/
|
||||
public HierarchicalState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the original state that received the message.
|
||||
*/
|
||||
public HierarchicalState getOriginalState() {
|
||||
return orgState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return as string
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("what=");
|
||||
sb.append(what);
|
||||
sb.append(" state=");
|
||||
sb.append(cn(state));
|
||||
sb.append(" orgState=");
|
||||
sb.append(cn(orgState));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an objects class name
|
||||
*/
|
||||
private String cn(Object n) {
|
||||
if (n == null) {
|
||||
return "null";
|
||||
} else {
|
||||
String name = n.getClass().getName();
|
||||
int lastDollar = name.lastIndexOf('$');
|
||||
return name.substring(lastDollar + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector<Info> mMessages = new Vector<Info>();
|
||||
private int mMaxSize = DEFAULT_SIZE;
|
||||
private int mOldestIndex = 0;
|
||||
private int mCount = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
ProcessedMessages() {
|
||||
}
|
||||
|
||||
ProcessedMessages(int maxSize) {
|
||||
setSize(maxSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set size of messages to maintain and clears all current messages.
|
||||
*
|
||||
* @param maxSize number of messages to maintain at anyone time.
|
||||
*/
|
||||
void setSize(int maxSize) {
|
||||
mMaxSize = maxSize;
|
||||
mCount = 0;
|
||||
mMessages.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of recent messages.
|
||||
*/
|
||||
int size() {
|
||||
return mMessages.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total number of messages processed since size was set.
|
||||
*/
|
||||
int count() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the information on a particular record. 0 is the oldest
|
||||
* record and size()-1 is the newest record. If the index is to
|
||||
* large null is returned.
|
||||
*/
|
||||
Info get(int index) {
|
||||
int nextIndex = mOldestIndex + index;
|
||||
if (nextIndex >= mMaxSize) {
|
||||
nextIndex -= mMaxSize;
|
||||
}
|
||||
if (nextIndex >= size()) {
|
||||
return null;
|
||||
} else {
|
||||
return mMessages.get(nextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a processed message.
|
||||
*
|
||||
* @param message
|
||||
* @param state that handled the message
|
||||
* @param orgState is the first state the received the message but
|
||||
* did not processes the message.
|
||||
*/
|
||||
void add(Message message, HierarchicalState state, HierarchicalState orgState) {
|
||||
mCount += 1;
|
||||
if (mMessages.size() < mMaxSize) {
|
||||
mMessages.add(new Info(message, state, orgState));
|
||||
} else {
|
||||
Info info = mMessages.get(mOldestIndex);
|
||||
mOldestIndex += 1;
|
||||
if (mOldestIndex >= mMaxSize) {
|
||||
mOldestIndex = 0;
|
||||
}
|
||||
info.update(message, state, orgState);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user