From 358a6643446ffd190539036f150511479c8b54b4 Mon Sep 17 00:00:00 2001 From: William Escande Date: Tue, 16 Nov 2021 16:01:35 +0100 Subject: [PATCH] statemachine migration from base to modules-utils Bug: 198418216 Tag: #refactor Test: atest ModulesUtilsTests Test: atest FrameworksUtilTests Merged-In: I65303c131f6e867f6c44d14baa537560fa8bf857 Change-Id: I65303c131f6e867f6c44d14baa537560fa8bf857 --- Android.bp | 1 + core/java/Android.bp | 21 - .../com/android/internal/util/IState.java | 73 - .../java/com/android/internal/util/State.java | 83 - .../android/internal/util/StateMachine.java | 2185 ----------------- .../internal/util/StateMachineTest.java | 2022 --------------- 6 files changed, 1 insertion(+), 4384 deletions(-) delete mode 100644 core/java/com/android/internal/util/IState.java delete mode 100644 core/java/com/android/internal/util/State.java delete mode 100644 core/java/com/android/internal/util/StateMachine.java delete mode 100644 core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java diff --git a/Android.bp b/Android.bp index ceb35bd6eb66..b0a1f93acd97 100644 --- a/Android.bp +++ b/Android.bp @@ -314,6 +314,7 @@ java_defaults { "tv_tuner_resource_manager_aidl_interface-java", "soundtrigger_middleware-aidl-java", "modules-utils-preconditions", + "modules-utils-statemachine", "modules-utils-synchronous-result-receiver", "modules-utils-os", "framework-permission-aidl-java", diff --git a/core/java/Android.bp b/core/java/Android.bp index 8081c153255d..a643c29b1da1 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -381,27 +381,6 @@ filegroup { ], } -java_library { - name: "modules-utils-statemachine", - srcs: [ - "com/android/internal/util/IState.java", - "com/android/internal/util/State.java", - "com/android/internal/util/StateMachine.java", - ], - libs: [ - "framework-annotations-lib", - "unsupportedappusage", - ], - sdk_version: "module_current", - min_sdk_version: "29", - - visibility: ["//visibility:public"], - apex_available: [ - "//apex_available:anyapex", - "//apex_available:platform", - ], -} - filegroup { name: "framework-ims-common-shared-srcs", srcs: [ diff --git a/core/java/com/android/internal/util/IState.java b/core/java/com/android/internal/util/IState.java deleted file mode 100644 index 41b3d5e0a11f..000000000000 --- a/core/java/com/android/internal/util/IState.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (C) 2011 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.compat.annotation.UnsupportedAppUsage; -import android.os.Message; - -/** - * {@hide} - * - * The interface for implementing states in a {@link StateMachine} - */ -public interface IState { - - /** - * Returned by processMessage to indicate the message was processed. - */ - static final boolean HANDLED = true; - - /** - * Returned by processMessage to indicate the message was NOT processed. - */ - static final boolean NOT_HANDLED = false; - - /** - * Called when a state is entered. - */ - void enter(); - - /** - * Called when a state is exited. - */ - void exit(); - - /** - * 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 HANDLED if processing has completed and NOT_HANDLED - * if the message wasn't processed. - */ - boolean processMessage(Message msg); - - /** - * Name of State for debugging purposes. - * - * @return name of state. - */ - @UnsupportedAppUsage - String getName(); -} diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java deleted file mode 100644 index d5c0f60f4b37..000000000000 --- a/core/java/com/android/internal/util/State.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 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.annotation.SuppressLint; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; -import android.os.Message; - -/** - * {@hide} - * - * The class for implementing states in a StateMachine - */ -@SuppressLint("AndroidFrameworkRequiresPermission") -public class State implements IState { - - /** - * Constructor - */ - @UnsupportedAppUsage - protected State() { - } - - /* (non-Javadoc) - * @see com.android.internal.util.IState#enter() - */ - @UnsupportedAppUsage - @Override - public void enter() { - } - - /* (non-Javadoc) - * @see com.android.internal.util.IState#exit() - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public void exit() { - } - - /* (non-Javadoc) - * @see com.android.internal.util.IState#processMessage(android.os.Message) - */ - @UnsupportedAppUsage - @Override - public boolean processMessage(Message msg) { - return false; - } - - /** - * Name of State for debugging purposes. - * - * This default implementation returns the class name, returning - * the instance name would better in cases where a State class - * is used for multiple states. But normally there is one class per - * state and the class name is sufficient and easy to get. You may - * want to provide a setName or some other mechanism for setting - * another name if the class name is not appropriate. - * - * @see com.android.internal.util.IState#processMessage(android.os.Message) - */ - @UnsupportedAppUsage - @Override - public String getName() { - String name = getClass().getName(); - int lastDollar = name.lastIndexOf('$'); - return name.substring(lastDollar + 1); - } -} diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java deleted file mode 100644 index cb8d9d127415..000000000000 --- a/core/java/com/android/internal/util/StateMachine.java +++ /dev/null @@ -1,2185 +0,0 @@ -/** - * 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.compat.annotation.UnsupportedAppUsage; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.text.TextUtils; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Vector; - -/** - * {@hide} - * - *

The state machine defined here is a hierarchical state machine which processes messages - * and can have states arranged hierarchically.

- * - *

A state is a State object and must implement - * processMessage and optionally enter/exit/getName. - * The enter/exit methods are equivalent to the construction and destruction - * in Object Oriented programming and are used to perform initialization and - * cleanup of the state respectively. The getName method returns the - * name of the state; the default implementation returns the class name. It may be - * desirable to have getName return the state instance name instead, - * in particular if a particular state class has multiple instances.

- * - *

When a state machine is created, addState is used to build the - * hierarchy and setInitialState is used to identify which of these - * is the initial state. After construction the programmer calls start - * which initializes and starts the state machine. The first action the StateMachine - * is to the invoke enter for all of the initial state's hierarchy, - * starting at its eldest parent. The calls to enter will be done in the context - * of the StateMachine's Handler, not in the context of the call to start, and they - * will be invoked before any messages are processed. For example, given the simple - * state machine below, mP1.enter will be invoked and then mS1.enter. Finally, - * messages sent to the state machine will be processed by the current state; - * in our simple state machine below that would initially be mS1.processMessage.

-
-        mP1
-       /   \
-      mS2   mS1 ----> initial state
-
- *

After the state machine is created and started, messages are sent to a state - * machine using sendMessage and the messages are created using - * obtainMessage. When the state machine receives a message the - * current state's processMessage is invoked. In the above example - * mS1.processMessage will be invoked first. The state may use transitionTo - * to change the current state to a new state.

- * - *

Each state in the state machine may have a zero or one parent states. If - * a child state is unable to handle a message it may have the message processed - * by its parent by returning false or NOT_HANDLED. If a message is not handled by - * a child state or any of its ancestors, unhandledMessage will be invoked - * to give one last chance for the state machine to process the message.

- * - *

When all processing is completed a state machine may choose to call - * transitionToHaltingState. When the current processingMessage - * returns the state machine will transfer to an internal HaltingState - * and invoke halting. Any message subsequently received by the state - * machine will cause haltedProcessMessage to be invoked.

- * - *

If it is desirable to completely stop the state machine call quit or - * quitNow. These will call exit of the current state and its parents, - * call onQuitting and then exit Thread/Loopers.

- * - *

In addition to processMessage each State has - * an enter method and exit method which may be overridden.

- * - *

Since the states are arranged in a hierarchy transitioning to a new state - * causes current states to be exited and new states to be entered. To determine - * the list of states to be entered/exited the common parent closest to - * the current state is found. We then exit from the current state and its - * parent's up to but not including the common parent state and then enter all - * of the new states below the common parent down to the destination state. - * If there is no common parent all states are exited and then the new states - * are entered.

- * - *

Two other methods that states can use are deferMessage and - * sendMessageAtFrontOfQueue. The sendMessageAtFrontOfQueue sends - * a message but places it on the front of the queue rather than the back. The - * deferMessage causes the message to be saved on a list until a - * transition is made to a new state. At which time all of the deferred messages - * will be put on the front of the state machine queue with the oldest message - * at the front. These will then be processed by the new current state before - * any other messages that are on the queue or might be added later. Both of - * these are protected and may only be invoked from within a state machine.

- * - *

To illustrate some of these properties we'll use state machine with an 8 - * state hierarchy:

-
-          mP0
-         /   \
-        mP1   mS0
-       /   \
-      mS2   mS1
-     /  \    \
-    mS3  mS4  mS5  ---> initial state
-
- *

After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. - * So the order of calling processMessage when a message is received is mS5, - * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this - * message by returning false or NOT_HANDLED.

- * - *

Now assume mS5.processMessage receives a message it can handle, and during - * the handling determines the machine should change states. It could call - * transitionTo(mS4) and return true or HANDLED. Immediately after returning from - * processMessage the state machine runtime will find the common parent, - * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then - * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So - * when the next message is received mS4.processMessage will be invoked.

- * - *

Now for some concrete examples, here is the canonical HelloWorld as a state machine. - * It responds with "Hello World" being printed to the log for every message.

-
-class HelloWorld extends StateMachine {
-    HelloWorld(String name) {
-        super(name);
-        addState(mState1);
-        setInitialState(mState1);
-    }
-
-    public static HelloWorld makeHelloWorld() {
-        HelloWorld hw = new HelloWorld("hw");
-        hw.start();
-        return hw;
-    }
-
-    class State1 extends State {
-        @Override public boolean processMessage(Message message) {
-            log("Hello World");
-            return HANDLED;
-        }
-    }
-    State1 mState1 = new State1();
-}
-
-void testHelloWorld() {
-    HelloWorld hw = makeHelloWorld();
-    hw.sendMessage(hw.obtainMessage());
-}
-
- *

A more interesting state machine is one with four states - * with two independent parent states.

-
-        mP1      mP2
-       /   \
-      mS2   mS1
-
- *

Here is a description of this state machine using pseudo code.

-
-state mP1 {
-     enter { log("mP1.enter"); }
-     exit { log("mP1.exit");  }
-     on msg {
-         CMD_2 {
-             send(CMD_3);
-             defer(msg);
-             transitionTo(mS2);
-             return HANDLED;
-         }
-         return NOT_HANDLED;
-     }
-}
-
-INITIAL
-state mS1 parent mP1 {
-     enter { log("mS1.enter"); }
-     exit  { log("mS1.exit");  }
-     on msg {
-         CMD_1 {
-             transitionTo(mS1);
-             return HANDLED;
-         }
-         return NOT_HANDLED;
-     }
-}
-
-state mS2 parent mP1 {
-     enter { log("mS2.enter"); }
-     exit  { log("mS2.exit");  }
-     on msg {
-         CMD_2 {
-             send(CMD_4);
-             return HANDLED;
-         }
-         CMD_3 {
-             defer(msg);
-             transitionTo(mP2);
-             return HANDLED;
-         }
-         return NOT_HANDLED;
-     }
-}
-
-state mP2 {
-     enter {
-         log("mP2.enter");
-         send(CMD_5);
-     }
-     exit { log("mP2.exit"); }
-     on msg {
-         CMD_3, CMD_4 { return HANDLED; }
-         CMD_5 {
-             transitionTo(HaltingState);
-             return HANDLED;
-         }
-         return NOT_HANDLED;
-     }
-}
-
- *

The implementation is below and also in StateMachineTest:

-
-class Hsm1 extends StateMachine {
-    public static final int CMD_1 = 1;
-    public static final int CMD_2 = 2;
-    public static final int CMD_3 = 3;
-    public static final int CMD_4 = 4;
-    public static final int CMD_5 = 5;
-
-    public static Hsm1 makeHsm1() {
-        log("makeHsm1 E");
-        Hsm1 sm = new Hsm1("hsm1");
-        sm.start();
-        log("makeHsm1 X");
-        return sm;
-    }
-
-    Hsm1(String name) {
-        super(name);
-        log("ctor E");
-
-        // Add states, use indentation to show hierarchy
-        addState(mP1);
-            addState(mS1, mP1);
-            addState(mS2, mP1);
-        addState(mP2);
-
-        // Set the initial state
-        setInitialState(mS1);
-        log("ctor X");
-    }
-
-    class P1 extends State {
-        @Override public void enter() {
-            log("mP1.enter");
-        }
-        @Override public boolean processMessage(Message message) {
-            boolean retVal;
-            log("mP1.processMessage what=" + message.what);
-            switch(message.what) {
-            case CMD_2:
-                // CMD_2 will arrive in mS2 before CMD_3
-                sendMessage(obtainMessage(CMD_3));
-                deferMessage(message);
-                transitionTo(mS2);
-                retVal = HANDLED;
-                break;
-            default:
-                // Any message we don't understand in this state invokes unhandledMessage
-                retVal = NOT_HANDLED;
-                break;
-            }
-            return retVal;
-        }
-        @Override public void exit() {
-            log("mP1.exit");
-        }
-    }
-
-    class S1 extends State {
-        @Override public void enter() {
-            log("mS1.enter");
-        }
-        @Override public boolean processMessage(Message message) {
-            log("S1.processMessage what=" + message.what);
-            if (message.what == CMD_1) {
-                // Transition to ourself to show that enter/exit is called
-                transitionTo(mS1);
-                return HANDLED;
-            } else {
-                // Let parent process all other messages
-                return NOT_HANDLED;
-            }
-        }
-        @Override public void exit() {
-            log("mS1.exit");
-        }
-    }
-
-    class S2 extends State {
-        @Override public void enter() {
-            log("mS2.enter");
-        }
-        @Override public boolean processMessage(Message message) {
-            boolean retVal;
-            log("mS2.processMessage what=" + message.what);
-            switch(message.what) {
-            case(CMD_2):
-                sendMessage(obtainMessage(CMD_4));
-                retVal = HANDLED;
-                break;
-            case(CMD_3):
-                deferMessage(message);
-                transitionTo(mP2);
-                retVal = HANDLED;
-                break;
-            default:
-                retVal = NOT_HANDLED;
-                break;
-            }
-            return retVal;
-        }
-        @Override public void exit() {
-            log("mS2.exit");
-        }
-    }
-
-    class P2 extends State {
-        @Override public void enter() {
-            log("mP2.enter");
-            sendMessage(obtainMessage(CMD_5));
-        }
-        @Override public boolean processMessage(Message message) {
-            log("P2.processMessage what=" + message.what);
-            switch(message.what) {
-            case(CMD_3):
-                break;
-            case(CMD_4):
-                break;
-            case(CMD_5):
-                transitionToHaltingState();
-                break;
-            }
-            return HANDLED;
-        }
-        @Override public void exit() {
-            log("mP2.exit");
-        }
-    }
-
-    @Override
-    void onHalting() {
-        log("halting");
-        synchronized (this) {
-            this.notifyAll();
-        }
-    }
-
-    P1 mP1 = new P1();
-    S1 mS1 = new S1();
-    S2 mS2 = new S2();
-    P2 mP2 = new P2();
-}
-
- *

If this is executed by sending two messages CMD_1 and CMD_2 - * (Note the synchronize is only needed because we use hsm.wait())

-
-Hsm1 hsm = makeHsm1();
-synchronize(hsm) {
-     hsm.sendMessage(obtainMessage(hsm.CMD_1));
-     hsm.sendMessage(obtainMessage(hsm.CMD_2));
-     try {
-          // wait for the messages to be handled
-          hsm.wait();
-     } catch (InterruptedException e) {
-          loge("exception while waiting " + e.getMessage());
-     }
-}
-
- *

The output is:

-
-D/hsm1    ( 1999): makeHsm1 E
-D/hsm1    ( 1999): ctor E
-D/hsm1    ( 1999): ctor X
-D/hsm1    ( 1999): mP1.enter
-D/hsm1    ( 1999): mS1.enter
-D/hsm1    ( 1999): makeHsm1 X
-D/hsm1    ( 1999): mS1.processMessage what=1
-D/hsm1    ( 1999): mS1.exit
-D/hsm1    ( 1999): mS1.enter
-D/hsm1    ( 1999): mS1.processMessage what=2
-D/hsm1    ( 1999): mP1.processMessage what=2
-D/hsm1    ( 1999): mS1.exit
-D/hsm1    ( 1999): mS2.enter
-D/hsm1    ( 1999): mS2.processMessage what=2
-D/hsm1    ( 1999): mS2.processMessage what=3
-D/hsm1    ( 1999): mS2.exit
-D/hsm1    ( 1999): mP1.exit
-D/hsm1    ( 1999): mP2.enter
-D/hsm1    ( 1999): mP2.processMessage what=3
-D/hsm1    ( 1999): mP2.processMessage what=4
-D/hsm1    ( 1999): mP2.processMessage what=5
-D/hsm1    ( 1999): mP2.exit
-D/hsm1    ( 1999): halting
-
- */ -public class StateMachine { - // Name of the state machine and used as logging tag - private String mName; - - /** Message.what value when quitting */ - private static final int SM_QUIT_CMD = -1; - - /** Message.what value when initializing */ - private static final int SM_INIT_CMD = -2; - - /** - * Convenience constant that maybe returned by processMessage - * to indicate the message was processed and is not to be - * processed by parent states - */ - public static final boolean HANDLED = true; - - /** - * Convenience constant that maybe returned by processMessage - * to indicate the message was NOT processed and is to be - * processed by parent states - */ - public static final boolean NOT_HANDLED = false; - - /** - * StateMachine logging record. - * {@hide} - */ - public static class LogRec { - private StateMachine mSm; - private long mTime; - private int mWhat; - private String mInfo; - private IState mState; - private IState mOrgState; - private IState mDstState; - - /** - * Constructor - * - * @param msg - * @param state the state which handled the message - * @param orgState is the first state the received the message but - * did not processes the message. - * @param transToState is the state that was transitioned to after the message was - * processed. - */ - LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState, - IState transToState) { - update(sm, msg, info, state, orgState, transToState); - } - - /** - * Update the information in the record. - * @param state that handled the message - * @param orgState is the first state the received the message - * @param dstState is the state that was the transition target when logging - */ - public void update(StateMachine sm, Message msg, String info, IState state, IState orgState, - IState dstState) { - mSm = sm; - mTime = System.currentTimeMillis(); - mWhat = (msg != null) ? msg.what : 0; - mInfo = info; - mState = state; - mOrgState = orgState; - mDstState = dstState; - } - - /** - * @return time stamp - */ - public long getTime() { - return mTime; - } - - /** - * @return msg.what - */ - public long getWhat() { - return mWhat; - } - - /** - * @return the command that was executing - */ - public String getInfo() { - return mInfo; - } - - /** - * @return the state that handled this message - */ - public IState getState() { - return mState; - } - - /** - * @return the state destination state if a transition is occurring or null if none. - */ - public IState getDestState() { - return mDstState; - } - - /** - * @return the original state that received the message. - */ - public IState getOriginalState() { - return mOrgState; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("time="); - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(mTime); - sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); - sb.append(" processed="); - sb.append(mState == null ? "" : mState.getName()); - sb.append(" org="); - sb.append(mOrgState == null ? "" : mOrgState.getName()); - sb.append(" dest="); - sb.append(mDstState == null ? "" : mDstState.getName()); - sb.append(" what="); - String what = mSm != null ? mSm.getWhatToString(mWhat) : ""; - if (TextUtils.isEmpty(what)) { - sb.append(mWhat); - sb.append("(0x"); - sb.append(Integer.toHexString(mWhat)); - sb.append(")"); - } else { - sb.append(what); - } - if (!TextUtils.isEmpty(mInfo)) { - sb.append(" "); - sb.append(mInfo); - } - return sb.toString(); - } - } - - /** - * A list of log records including messages recently processed by the state machine. - * - * The class maintains a list of log records including messages - * 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 records, - * count which is the number of records processed since the - * the last setSize, get which returns a record and - * add which adds a record. - */ - private static class LogRecords { - - private static final int DEFAULT_SIZE = 20; - - private Vector mLogRecVector = new Vector(); - private int mMaxSize = DEFAULT_SIZE; - private int mOldestIndex = 0; - private int mCount = 0; - private boolean mLogOnlyTransitions = false; - - /** - * private constructor use add - */ - private LogRecords() { - } - - /** - * Set size of messages to maintain and clears all current records. - * - * @param maxSize number of records to maintain at anyone time. - */ - synchronized void setSize(int maxSize) { - // TODO: once b/28217358 is fixed, add unit tests to verify that these variables are - // cleared after calling this method, and that subsequent calls to get() function as - // expected. - mMaxSize = maxSize; - mOldestIndex = 0; - mCount = 0; - mLogRecVector.clear(); - } - - synchronized void setLogOnlyTransitions(boolean enable) { - mLogOnlyTransitions = enable; - } - - synchronized boolean logOnlyTransitions() { - return mLogOnlyTransitions; - } - - /** - * @return the number of recent records. - */ - synchronized int size() { - return mLogRecVector.size(); - } - - /** - * @return the total number of records processed since size was set. - */ - synchronized int count() { - return mCount; - } - - /** - * Clear the list of records. - */ - synchronized void cleanup() { - mLogRecVector.clear(); - } - - /** - * @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. - */ - synchronized LogRec get(int index) { - int nextIndex = mOldestIndex + index; - if (nextIndex >= mMaxSize) { - nextIndex -= mMaxSize; - } - if (nextIndex >= size()) { - return null; - } else { - return mLogRecVector.get(nextIndex); - } - } - - /** - * Add a processed message. - * - * @param msg - * @param messageInfo to be stored - * @param state that handled the message - * @param orgState is the first state the received the message but - * did not processes the message. - * @param transToState is the state that was transitioned to after the message was - * processed. - * - */ - synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state, - IState orgState, IState transToState) { - mCount += 1; - if (mLogRecVector.size() < mMaxSize) { - mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState)); - } else { - LogRec pmi = mLogRecVector.get(mOldestIndex); - mOldestIndex += 1; - if (mOldestIndex >= mMaxSize) { - mOldestIndex = 0; - } - pmi.update(sm, msg, messageInfo, state, orgState, transToState); - } - } - } - - private static class SmHandler extends Handler { - - /** true if StateMachine has quit */ - private boolean mHasQuit = false; - - /** The debug flag */ - private boolean mDbg = false; - - /** The SmHandler object, identifies that message is internal */ - private static final Object mSmHandlerObj = new Object(); - - /** The current message */ - private Message mMsg; - - /** A list of log records including messages this state machine has processed */ - private LogRecords mLogRecords = new LogRecords(); - - /** true if construction of the state machine has not been completed */ - private boolean mIsConstructionCompleted; - - /** Stack used to manage the current hierarchy of states */ - private StateInfo mStateStack[]; - - /** Top of mStateStack */ - private int mStateStackTopIndex = -1; - - /** A temporary stack used to manage the state stack */ - private StateInfo mTempStateStack[]; - - /** The top of the mTempStateStack */ - private int mTempStateStackCount; - - /** State used when state machine is halted */ - private HaltingState mHaltingState = new HaltingState(); - - /** State used when state machine is quitting */ - private QuittingState mQuittingState = new QuittingState(); - - /** Reference to the StateMachine */ - private StateMachine mSm; - - /** - * Information about a state. - * Used to maintain the hierarchy. - */ - private class StateInfo { - /** The state */ - State state; - - /** The parent of this state, null if there is no parent */ - StateInfo parentStateInfo; - - /** True when the state has been entered and on the stack */ - boolean active; - - /** - * Convert StateInfo to string - */ - @Override - public String toString() { - return "state=" + state.getName() + ",active=" + active + ",parent=" - + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName()); - } - } - - /** The map of all of the states in the state machine */ - private HashMap mStateInfo = new HashMap(); - - /** The initial state that will process the first message */ - private State mInitialState; - - /** The destination state when transitionTo has been invoked */ - private State mDestState; - - /** - * Indicates if a transition is in progress - * - * This will be true for all calls of State.exit and all calls of State.enter except for the - * last enter call for the current destination state. - */ - private boolean mTransitionInProgress = false; - - /** The list of deferred messages */ - private ArrayList mDeferredMessages = new ArrayList(); - - /** - * State entered when transitionToHaltingState is called. - */ - private class HaltingState extends State { - @Override - public boolean processMessage(Message msg) { - mSm.haltedProcessMessage(msg); - return true; - } - } - - /** - * State entered when a valid quit message is handled. - */ - private class QuittingState extends State { - @Override - public boolean processMessage(Message msg) { - return NOT_HANDLED; - } - } - - /** - * 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. - */ - @Override - public final void handleMessage(Message msg) { - if (!mHasQuit) { - if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { - mSm.onPreHandleMessage(msg); - } - - if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what); - - /** Save the current message */ - mMsg = msg; - - /** State that processed the message */ - State msgProcessedState = null; - if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) { - /** Normal path */ - msgProcessedState = processMsg(msg); - } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD) - && (mMsg.obj == mSmHandlerObj)) { - /** Initial one time path. */ - mIsConstructionCompleted = true; - invokeEnterMethods(0); - } else { - throw new RuntimeException("StateMachine.handleMessage: " - + "The start method not called, received msg: " + msg); - } - performTransitions(msgProcessedState, msg); - - // We need to check if mSm == null here as we could be quitting. - if (mDbg && mSm != null) mSm.log("handleMessage: X"); - - if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { - mSm.onPostHandleMessage(msg); - } - } - } - - /** - * Do any transitions - * @param msgProcessedState is the state that processed the message - */ - private void performTransitions(State msgProcessedState, Message msg) { - /** - * If transitionTo has been called, exit and then enter - * the appropriate states. We loop on this to allow - * enter and exit methods to use transitionTo. - */ - State orgState = mStateStack[mStateStackTopIndex].state; - - /** - * Record whether message needs to be logged before we transition and - * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which - * always set msg.obj to the handler. - */ - boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj); - - if (mLogRecords.logOnlyTransitions()) { - /** Record only if there is a transition */ - if (mDestState != null) { - mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, - orgState, mDestState); - } - } else if (recordLogMsg) { - /** Record message */ - mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState, - mDestState); - } - - State destState = mDestState; - if (destState != null) { - /** - * Process the transitions including transitions in the enter/exit methods - */ - while (true) { - if (mDbg) mSm.log("handleMessage: new destination call exit/enter"); - - /** - * Determine the states to exit and enter and return the - * common ancestor state of the enter/exit states. Then - * invoke the exit methods then the enter methods. - */ - StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); - // flag is cleared in invokeEnterMethods before entering the target state - mTransitionInProgress = true; - invokeExitMethods(commonStateInfo); - int stateStackEnteringIndex = moveTempStateStackToStateStack(); - invokeEnterMethods(stateStackEnteringIndex); - - /** - * Since we have transitioned to a new state we need to have - * any deferred messages moved to the front of the message queue - * so they will be processed before any other messages in the - * message queue. - */ - moveDeferredMessageAtFrontOfQueue(); - - if (destState != mDestState) { - // A new mDestState so continue looping - destState = mDestState; - } else { - // No change in mDestState so we're done - break; - } - } - mDestState = null; - } - - /** - * After processing all transitions check and - * see if the last transition was to quit or halt. - */ - if (destState != null) { - if (destState == mQuittingState) { - /** - * Call onQuitting to let subclasses cleanup. - */ - mSm.onQuitting(); - cleanupAfterQuitting(); - } else if (destState == mHaltingState) { - /** - * Call onHalting() if we've transitioned to the halting - * state. All subsequent messages will be processed in - * in the halting state which invokes haltedProcessMessage(msg); - */ - mSm.onHalting(); - } - } - } - - /** - * Cleanup all the static variables and the looper after the SM has been quit. - */ - private final void cleanupAfterQuitting() { - if (mSm.mSmThread != null) { - // If we made the thread then quit looper which stops the thread. - getLooper().quit(); - mSm.mSmThread = null; - } - - mSm.mSmHandler = null; - mSm = null; - mMsg = null; - mLogRecords.cleanup(); - mStateStack = null; - mTempStateStack = null; - mStateInfo.clear(); - mInitialState = null; - mDestState = null; - mDeferredMessages.clear(); - mHasQuit = true; - } - - /** - * Complete the construction of the state machine. - */ - private final void completeConstruction() { - if (mDbg) mSm.log("completeConstruction: E"); - - /** - * Determine the maximum depth of the state hierarchy - * so we can allocate the state stacks. - */ - int maxDepth = 0; - for (StateInfo si : mStateInfo.values()) { - int depth = 0; - for (StateInfo i = si; i != null; depth++) { - i = i.parentStateInfo; - } - if (maxDepth < depth) { - maxDepth = depth; - } - } - if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth); - - mStateStack = new StateInfo[maxDepth]; - mTempStateStack = new StateInfo[maxDepth]; - setupInitialStateStack(); - - /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */ - sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); - - if (mDbg) mSm.log("completeConstruction: X"); - } - - /** - * Process the message. If the current state doesn't handle - * it, call the states parent and so on. If it is never handled then - * call the state machines unhandledMessage method. - * @return the state that processed the message - */ - private final State processMsg(Message msg) { - StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; - if (mDbg) { - mSm.log("processMsg: " + curStateInfo.state.getName()); - } - - if (isQuit(msg)) { - transitionTo(mQuittingState); - } else { - while (!curStateInfo.state.processMessage(msg)) { - /** - * Not processed - */ - curStateInfo = curStateInfo.parentStateInfo; - if (curStateInfo == null) { - /** - * No parents left so it's not handled - */ - mSm.unhandledMessage(msg); - break; - } - if (mDbg) { - mSm.log("processMsg: " + curStateInfo.state.getName()); - } - } - } - return (curStateInfo != null) ? curStateInfo.state : null; - } - - /** - * Call the exit method for each state from the top of stack - * up to the common ancestor state. - */ - private final void invokeExitMethods(StateInfo commonStateInfo) { - while ((mStateStackTopIndex >= 0) - && (mStateStack[mStateStackTopIndex] != commonStateInfo)) { - State curState = mStateStack[mStateStackTopIndex].state; - if (mDbg) mSm.log("invokeExitMethods: " + curState.getName()); - curState.exit(); - mStateStack[mStateStackTopIndex].active = false; - mStateStackTopIndex -= 1; - } - } - - /** - * Invoke the enter method starting at the entering index to top of state stack - */ - private final void invokeEnterMethods(int stateStackEnteringIndex) { - for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { - if (stateStackEnteringIndex == mStateStackTopIndex) { - // Last enter state for transition - mTransitionInProgress = false; - } - if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName()); - mStateStack[i].state.enter(); - mStateStack[i].active = true; - } - mTransitionInProgress = false; // ensure flag set to false if no methods called - } - - /** - * Move the deferred message to the front of the message queue. - */ - private final void moveDeferredMessageAtFrontOfQueue() { - /** - * The oldest messages on the deferred list must be at - * the front of the queue so start at the back, which - * as the most resent message and end with the oldest - * messages at the front of the queue. - */ - for (int i = mDeferredMessages.size() - 1; i >= 0; i--) { - Message curMsg = mDeferredMessages.get(i); - if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what); - sendMessageAtFrontOfQueue(curMsg); - } - mDeferredMessages.clear(); - } - - /** - * Move the contents of the temporary stack to the state stack - * reversing the order of the items on the temporary stack as - * they are moved. - * - * @return index into mStateStack where entering needs to start - */ - private final int moveTempStateStackToStateStack() { - int startingIndex = mStateStackTopIndex + 1; - int i = mTempStateStackCount - 1; - int j = startingIndex; - while (i >= 0) { - if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j); - mStateStack[j] = mTempStateStack[i]; - j += 1; - i -= 1; - } - - mStateStackTopIndex = j - 1; - if (mDbg) { - mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex - + ",startingIndex=" + startingIndex + ",Top=" - + mStateStack[mStateStackTopIndex].state.getName()); - } - return startingIndex; - } - - /** - * Setup the mTempStateStack with the states we are going to enter. - * - * This is found by searching up the destState's ancestors for a - * state that is already active i.e. StateInfo.active == true. - * The destStae and all of its inactive parents will be on the - * TempStateStack as the list of states to enter. - * - * @return StateInfo of the common ancestor for the destState and - * current state or null if there is no common parent. - */ - private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { - /** - * Search up the parent list of the destination state for an active - * state. Use a do while() loop as the destState must always be entered - * even if it is active. This can happen if we are exiting/entering - * the current state. - */ - mTempStateStackCount = 0; - StateInfo curStateInfo = mStateInfo.get(destState); - do { - mTempStateStack[mTempStateStackCount++] = curStateInfo; - curStateInfo = curStateInfo.parentStateInfo; - } while ((curStateInfo != null) && !curStateInfo.active); - - if (mDbg) { - mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount=" - + mTempStateStackCount + ",curStateInfo: " + curStateInfo); - } - return curStateInfo; - } - - /** - * Initialize StateStack to mInitialState. - */ - private final void setupInitialStateStack() { - if (mDbg) { - mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName()); - } - - StateInfo curStateInfo = mStateInfo.get(mInitialState); - for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { - mTempStateStack[mTempStateStackCount] = curStateInfo; - curStateInfo = curStateInfo.parentStateInfo; - } - - // Empty the StateStack - mStateStackTopIndex = -1; - - moveTempStateStackToStateStack(); - } - - /** - * @return current message - */ - private final Message getCurrentMessage() { - return mMsg; - } - - /** - * @return current state - */ - private final IState getCurrentState() { - return mStateStack[mStateStackTopIndex].state; - } - - /** - * Add a new state to the state machine. Bottom up addition - * of states is allowed but the same state may only exist - * in one hierarchy. - * - * @param state the state to add - * @param parent the parent of state - * @return stateInfo for this state - */ - private final StateInfo addState(State state, State parent) { - if (mDbg) { - mSm.log("addStateInternal: E state=" + state.getName() + ",parent=" - + ((parent == null) ? "" : parent.getName())); - } - StateInfo parentStateInfo = null; - if (parent != null) { - parentStateInfo = mStateInfo.get(parent); - if (parentStateInfo == null) { - // Recursively add our parent as it's not been added yet. - parentStateInfo = addState(parent, null); - } - } - StateInfo stateInfo = mStateInfo.get(state); - if (stateInfo == null) { - stateInfo = new StateInfo(); - mStateInfo.put(state, stateInfo); - } - - // Validate that we aren't adding the same state in two different hierarchies. - if ((stateInfo.parentStateInfo != null) - && (stateInfo.parentStateInfo != parentStateInfo)) { - throw new RuntimeException("state already added"); - } - stateInfo.state = state; - stateInfo.parentStateInfo = parentStateInfo; - stateInfo.active = false; - if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo); - return stateInfo; - } - - /** - * Remove a state from the state machine. Will not remove the state if it is currently - * active or if it has any children in the hierarchy. - * @param state the state to remove - */ - private void removeState(State state) { - StateInfo stateInfo = mStateInfo.get(state); - if (stateInfo == null || stateInfo.active) { - return; - } - boolean isParent = mStateInfo.values().stream() - .filter(si -> si.parentStateInfo == stateInfo) - .findAny() - .isPresent(); - if (isParent) { - return; - } - mStateInfo.remove(state); - } - - /** - * Constructor - * - * @param looper for dispatching messages - * @param sm the hierarchical state machine - */ - private SmHandler(Looper looper, StateMachine sm) { - super(looper); - mSm = sm; - - addState(mHaltingState, null); - addState(mQuittingState, null); - } - - /** @see StateMachine#setInitialState(State) */ - private final void setInitialState(State initialState) { - if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName()); - mInitialState = initialState; - } - - /** @see StateMachine#transitionTo(IState) */ - private final void transitionTo(IState destState) { - if (mTransitionInProgress) { - Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " + - mDestState + ", new target state=" + destState); - } - mDestState = (State) destState; - if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName()); - } - - /** @see StateMachine#deferMessage(Message) */ - private final void deferMessage(Message msg) { - if (mDbg) mSm.log("deferMessage: msg=" + msg.what); - - /* Copy the "msg" to "newMsg" as "msg" will be recycled */ - Message newMsg = obtainMessage(); - newMsg.copyFrom(msg); - - mDeferredMessages.add(newMsg); - } - - /** @see StateMachine#quit() */ - private final void quit() { - if (mDbg) mSm.log("quit:"); - sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); - } - - /** @see StateMachine#quitNow() */ - private final void quitNow() { - if (mDbg) mSm.log("quitNow:"); - sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); - } - - /** Validate that the message was sent by quit or quitNow. */ - private final boolean isQuit(Message msg) { - return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj); - } - - /** @see StateMachine#isDbg() */ - private final boolean isDbg() { - return mDbg; - } - - /** @see StateMachine#setDbg(boolean) */ - private final void setDbg(boolean dbg) { - mDbg = dbg; - } - - } - - private SmHandler mSmHandler; - private HandlerThread mSmThread; - - /** - * Initialize. - * - * @param looper for this state machine - * @param name of the state machine - */ - private void initStateMachine(String name, Looper looper) { - mName = name; - mSmHandler = new SmHandler(looper, this); - } - - /** - * Constructor creates a StateMachine with its own thread. - * - * @param name of the state machine - */ - @UnsupportedAppUsage - protected StateMachine(String name) { - mSmThread = new HandlerThread(name); - mSmThread.start(); - Looper looper = mSmThread.getLooper(); - - initStateMachine(name, looper); - } - - /** - * Constructor creates a StateMachine using the looper. - * - * @param name of the state machine - */ - @UnsupportedAppUsage - protected StateMachine(String name, Looper looper) { - initStateMachine(name, looper); - } - - /** - * Constructor creates a StateMachine using the handler. - * - * @param name of the state machine - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected StateMachine(String name, Handler handler) { - initStateMachine(name, handler.getLooper()); - } - - /** - * Notifies subclass that the StateMachine handler is about to process the Message msg - * @param msg The message that is being handled - */ - protected void onPreHandleMessage(Message msg) { - } - - /** - * Notifies subclass that the StateMachine handler has finished processing the Message msg and - * has possibly transitioned to a new state. - * @param msg The message that is being handled - */ - protected void onPostHandleMessage(Message msg) { - } - - /** - * Add a new state to the state machine - * @param state the state to add - * @param parent the parent of state - */ - public final void addState(State state, State parent) { - mSmHandler.addState(state, parent); - } - - /** - * Add a new state to the state machine, parent will be null - * @param state to add - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public final void addState(State state) { - mSmHandler.addState(state, null); - } - - /** - * Removes a state from the state machine, unless it is currently active or if it has children. - * @param state state to remove - */ - public final void removeState(State state) { - mSmHandler.removeState(state); - } - - /** - * Set the initial state. This must be invoked before - * and messages are sent to the state machine. - * - * @param initialState is the state which will receive the first message. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public final void setInitialState(State initialState) { - mSmHandler.setInitialState(initialState); - } - - /** - * @return current message - */ - public final Message getCurrentMessage() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return null; - return smh.getCurrentMessage(); - } - - /** - * @return current state - */ - public final IState getCurrentState() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return null; - return smh.getCurrentState(); - } - - /** - * 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. - * - * this function can also be called inside the enter function of the - * previous transition target, but the behavior is undefined when it is - * called mid-way through a previous transition (for example, calling this - * in the enter() routine of a intermediate node when the current transition - * target is one of the nodes descendants). - * - * @param destState will be the state that receives the next message. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public final void transitionTo(IState destState) { - mSmHandler.transitionTo(destState); - } - - /** - * transition to halt state. Upon returning - * from processMessage we will exit all current - * states, execute the onHalting() method and then - * for all subsequent messages haltedProcessMessage - * will be called. - */ - public final void transitionToHaltingState() { - mSmHandler.transitionTo(mSmHandler.mHaltingState); - } - - /** - * 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) - * - * @param msg is deferred until the next transition. - */ - public final void deferMessage(Message msg) { - mSmHandler.deferMessage(msg); - } - - /** - * Called when message wasn't handled - * - * @param msg that couldn't be handled. - */ - protected void unhandledMessage(Message msg) { - if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what); - } - - /** - * Called for any message that is received after - * transitionToHalting is called. - */ - protected void haltedProcessMessage(Message msg) { - } - - /** - * This will be called once after handling a message that called - * transitionToHalting. All subsequent messages will invoke - * {@link StateMachine#haltedProcessMessage(Message)} - */ - protected void onHalting() { - } - - /** - * This will be called once after a quit message that was NOT handled by - * the derived StateMachine. The StateMachine will stop and any subsequent messages will be - * ignored. In addition, if this StateMachine created the thread, the thread will - * be stopped after this method returns. - */ - protected void onQuitting() { - } - - /** - * @return the name - */ - public final String getName() { - return mName; - } - - /** - * Set number of log records to maintain and clears all current records. - * - * @param maxSize number of messages to maintain at anyone time. - */ - public final void setLogRecSize(int maxSize) { - mSmHandler.mLogRecords.setSize(maxSize); - } - - /** - * Set to log only messages that cause a state transition - * - * @param enable {@code true} to enable, {@code false} to disable - */ - public final void setLogOnlyTransitions(boolean enable) { - mSmHandler.mLogRecords.setLogOnlyTransitions(enable); - } - - /** - * @return the number of log records currently readable - */ - public final int getLogRecSize() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return 0; - return smh.mLogRecords.size(); - } - - /** - * @return the number of log records we can store - */ - @VisibleForTesting - public final int getLogRecMaxSize() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return 0; - return smh.mLogRecords.mMaxSize; - } - - /** - * @return the total number of records processed - */ - public final int getLogRecCount() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return 0; - return smh.mLogRecords.count(); - } - - /** - * @return a log record, or null if index is out of range - */ - public final LogRec getLogRec(int index) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return null; - return smh.mLogRecords.get(index); - } - - /** - * @return a copy of LogRecs as a collection - */ - public final Collection copyLogRecs() { - Vector vlr = new Vector(); - SmHandler smh = mSmHandler; - if (smh != null) { - for (LogRec lr : smh.mLogRecords.mLogRecVector) { - vlr.add(lr); - } - } - return vlr; - } - - /** - * Add the string to LogRecords. - * - * @param string - */ - public void addLogRec(String string) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(), - smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState); - } - - /** - * @return true if msg should be saved in the log, default is true. - */ - protected boolean recordLogRec(Message msg) { - return true; - } - - /** - * Return a string to be logged by LogRec, default - * is an empty string. Override if additional information is desired. - * - * @param msg that was processed - * @return information to be logged as a String - */ - protected String getLogRecString(Message msg) { - return ""; - } - - /** - * @return the string for msg.what - */ - protected String getWhatToString(int what) { - return null; - } - - /** - * @return Handler, maybe null if state machine has quit. - */ - public final Handler getHandler() { - return mSmHandler; - } - - /** - * Get a message and set Message.target state machine handler. - * - * Note: The handler can be null if the state machine has quit, - * which means target will be null and may cause a AndroidRuntimeException - * in MessageQueue#enqueMessage if sent directly or if sent using - * StateMachine#sendMessage the message will just be ignored. - * - * @return A Message object from the global pool - */ - public final Message obtainMessage() { - return Message.obtain(mSmHandler); - } - - /** - * Get a message and set Message.target state machine handler, what. - * - * Note: The handler can be null if the state machine has quit, - * which means target will be null and may cause a AndroidRuntimeException - * in MessageQueue#enqueMessage if sent directly or if sent using - * StateMachine#sendMessage the message will just be ignored. - * - * @param what is the assigned to Message.what. - * @return A Message object from the global pool - */ - public final Message obtainMessage(int what) { - return Message.obtain(mSmHandler, what); - } - - /** - * Get a message and set Message.target state machine handler, - * what and obj. - * - * Note: The handler can be null if the state machine has quit, - * which means target will be null and may cause a AndroidRuntimeException - * in MessageQueue#enqueMessage if sent directly or if sent using - * StateMachine#sendMessage the message will just be ignored. - * - * @param what is the assigned to Message.what. - * @param obj is assigned to Message.obj. - * @return A Message object from the global pool - */ - public final Message obtainMessage(int what, Object obj) { - return Message.obtain(mSmHandler, what, obj); - } - - /** - * Get a message and set Message.target state machine handler, - * what, arg1 and arg2 - * - * Note: The handler can be null if the state machine has quit, - * which means target will be null and may cause a AndroidRuntimeException - * in MessageQueue#enqueMessage if sent directly or if sent using - * StateMachine#sendMessage the message will just be ignored. - * - * @param what is assigned to Message.what - * @param arg1 is assigned to Message.arg1 - * @return A Message object from the global pool - */ - public final Message obtainMessage(int what, int arg1) { - // use this obtain so we don't match the obtain(h, what, Object) method - return Message.obtain(mSmHandler, what, arg1, 0); - } - - /** - * Get a message and set Message.target state machine handler, - * what, arg1 and arg2 - * - * Note: The handler can be null if the state machine has quit, - * which means target will be null and may cause a AndroidRuntimeException - * in MessageQueue#enqueMessage if sent directly or if sent using - * StateMachine#sendMessage the message will just be ignored. - * - * @param what is assigned to Message.what - * @param arg1 is assigned to Message.arg1 - * @param arg2 is assigned to Message.arg2 - * @return A Message object from the global pool - */ - @UnsupportedAppUsage - public final Message obtainMessage(int what, int arg1, int arg2) { - return Message.obtain(mSmHandler, what, arg1, arg2); - } - - /** - * Get a message and set Message.target state machine handler, - * what, arg1, arg2 and obj - * - * Note: The handler can be null if the state machine has quit, - * which means target will be null and may cause a AndroidRuntimeException - * in MessageQueue#enqueMessage if sent directly or if sent using - * StateMachine#sendMessage the message will just be ignored. - * - * @param what is assigned to Message.what - * @param arg1 is assigned to Message.arg1 - * @param arg2 is assigned to Message.arg2 - * @param obj is assigned to Message.obj - * @return A Message object from the global pool - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public final Message obtainMessage(int what, int arg1, int arg2, Object obj) { - return Message.obtain(mSmHandler, what, arg1, arg2, obj); - } - - /** - * Enqueue a message to this state machine. - * - * Message is ignored if state machine has quit. - */ - @UnsupportedAppUsage - public void sendMessage(int what) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessage(obtainMessage(what)); - } - - /** - * Enqueue a message to this state machine. - * - * Message is ignored if state machine has quit. - */ - @UnsupportedAppUsage - public void sendMessage(int what, Object obj) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessage(obtainMessage(what, obj)); - } - - /** - * Enqueue a message to this state machine. - * - * Message is ignored if state machine has quit. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void sendMessage(int what, int arg1) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessage(obtainMessage(what, arg1)); - } - - /** - * Enqueue a message to this state machine. - * - * Message is ignored if state machine has quit. - */ - public void sendMessage(int what, int arg1, int arg2) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessage(obtainMessage(what, arg1, arg2)); - } - - /** - * Enqueue a message to this state machine. - * - * Message is ignored if state machine has quit. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void sendMessage(int what, int arg1, int arg2, Object obj) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessage(obtainMessage(what, arg1, arg2, obj)); - } - - /** - * Enqueue a message to this state machine. - * - * Message is ignored if state machine has quit. - */ - @UnsupportedAppUsage - public void sendMessage(Message msg) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessage(msg); - } - - /** - * Enqueue a message to this state machine after a delay. - * - * Message is ignored if state machine has quit. - */ - public void sendMessageDelayed(int what, long delayMillis) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageDelayed(obtainMessage(what), delayMillis); - } - - /** - * Enqueue a message to this state machine after a delay. - * - * Message is ignored if state machine has quit. - */ - public void sendMessageDelayed(int what, Object obj, long delayMillis) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis); - } - - /** - * Enqueue a message to this state machine after a delay. - * - * Message is ignored if state machine has quit. - */ - public void sendMessageDelayed(int what, int arg1, long delayMillis) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis); - } - - /** - * Enqueue a message to this state machine after a delay. - * - * Message is ignored if state machine has quit. - */ - public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis); - } - - /** - * Enqueue a message to this state machine after a delay. - * - * Message is ignored if state machine has quit. - */ - public void sendMessageDelayed(int what, int arg1, int arg2, Object obj, - long delayMillis) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis); - } - - /** - * Enqueue a message to this state machine after a delay. - * - * Message is ignored if state machine has quit. - */ - public void sendMessageDelayed(Message msg, long delayMillis) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageDelayed(msg, delayMillis); - } - - /** - * Enqueue a message to the front of the queue for this state machine. - * Protected, may only be called by instances of StateMachine. - * - * Message is ignored if state machine has quit. - */ - protected final void sendMessageAtFrontOfQueue(int what) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageAtFrontOfQueue(obtainMessage(what)); - } - - /** - * Enqueue a message to the front of the queue for this state machine. - * Protected, may only be called by instances of StateMachine. - * - * Message is ignored if state machine has quit. - */ - protected final void sendMessageAtFrontOfQueue(int what, Object obj) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj)); - } - - /** - * Enqueue a message to the front of the queue for this state machine. - * Protected, may only be called by instances of StateMachine. - * - * Message is ignored if state machine has quit. - */ - protected final void sendMessageAtFrontOfQueue(int what, int arg1) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1)); - } - - - /** - * Enqueue a message to the front of the queue for this state machine. - * Protected, may only be called by instances of StateMachine. - * - * Message is ignored if state machine has quit. - */ - protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2)); - } - - /** - * Enqueue a message to the front of the queue for this state machine. - * Protected, may only be called by instances of StateMachine. - * - * Message is ignored if state machine has quit. - */ - protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj)); - } - - /** - * Enqueue a message to the front of the queue for this state machine. - * Protected, may only be called by instances of StateMachine. - * - * Message is ignored if state machine has quit. - */ - protected final void sendMessageAtFrontOfQueue(Message msg) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.sendMessageAtFrontOfQueue(msg); - } - - /** - * Removes a message from the message queue. - * Protected, may only be called by instances of StateMachine. - */ - protected final void removeMessages(int what) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.removeMessages(what); - } - - /** - * Removes a message from the deferred messages queue. - */ - protected final void removeDeferredMessages(int what) { - SmHandler smh = mSmHandler; - if (smh == null) return; - - Iterator iterator = smh.mDeferredMessages.iterator(); - while (iterator.hasNext()) { - Message msg = iterator.next(); - if (msg.what == what) iterator.remove(); - } - } - - /** - * Check if there are any pending messages with code 'what' in deferred messages queue. - */ - protected final boolean hasDeferredMessages(int what) { - SmHandler smh = mSmHandler; - if (smh == null) return false; - - Iterator iterator = smh.mDeferredMessages.iterator(); - while (iterator.hasNext()) { - Message msg = iterator.next(); - if (msg.what == what) return true; - } - - return false; - } - - /** - * Check if there are any pending posts of messages with code 'what' in - * the message queue. This does NOT check messages in deferred message queue. - */ - protected final boolean hasMessages(int what) { - SmHandler smh = mSmHandler; - if (smh == null) return false; - - return smh.hasMessages(what); - } - - /** - * Validate that the message was sent by - * {@link StateMachine#quit} or {@link StateMachine#quitNow}. - * */ - protected final boolean isQuit(Message msg) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return msg.what == SM_QUIT_CMD; - - return smh.isQuit(msg); - } - - /** - * Quit the state machine after all currently queued up messages are processed. - */ - public final void quit() { - // mSmHandler can be null if the state machine is already stopped. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.quit(); - } - - /** - * Quit the state machine immediately all currently queued messages will be discarded. - */ - public final void quitNow() { - // mSmHandler can be null if the state machine is already stopped. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.quitNow(); - } - - /** - * @return if debugging is enabled - */ - public boolean isDbg() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return false; - - return smh.isDbg(); - } - - /** - * Set debug enable/disabled. - * - * @param dbg is true to enable debugging. - */ - public void setDbg(boolean dbg) { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - smh.setDbg(dbg); - } - - /** - * Start the state machine. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void start() { - // mSmHandler can be null if the state machine has quit. - SmHandler smh = mSmHandler; - if (smh == null) return; - - /** Send the complete construction message */ - smh.completeConstruction(); - } - - /** - * Dump the current state. - * - * @param fd - * @param pw - * @param args - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(getName() + ":"); - pw.println(" total records=" + getLogRecCount()); - for (int i = 0; i < getLogRecSize(); i++) { - pw.println(" rec[" + i + "]: " + getLogRec(i)); - pw.flush(); - } - final IState curState = getCurrentState(); - pw.println("curState=" + (curState == null ? "" : curState.getName())); - } - - @Override - public String toString() { - String name = "(null)"; - String state = "(null)"; - try { - name = mName.toString(); - state = mSmHandler.getCurrentState().getName().toString(); - } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { - // Will use default(s) initialized above. - } - return "name=" + name + " state=" + state; - } - - /** - * Log with debug and add to the LogRecords. - * - * @param s is string log - */ - protected void logAndAddLogRec(String s) { - addLogRec(s); - log(s); - } - - /** - * Log with debug - * - * @param s is string log - */ - protected void log(String s) { - Log.d(mName, s); - } - - /** - * Log with debug attribute - * - * @param s is string log - */ - protected void logd(String s) { - Log.d(mName, s); - } - - /** - * Log with verbose attribute - * - * @param s is string log - */ - protected void logv(String s) { - Log.v(mName, s); - } - - /** - * Log with info attribute - * - * @param s is string log - */ - protected void logi(String s) { - Log.i(mName, s); - } - - /** - * Log with warning attribute - * - * @param s is string log - */ - protected void logw(String s) { - Log.w(mName, s); - } - - /** - * Log with error attribute - * - * @param s is string log - */ - protected void loge(String s) { - Log.e(mName, s); - } - - /** - * Log with error attribute - * - * @param s is string log - * @param e is a Throwable which logs additional information. - */ - protected void loge(String s, Throwable e) { - Log.e(mName, s, e); - } -} diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java deleted file mode 100644 index b85cb9cec47d..000000000000 --- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java +++ /dev/null @@ -1,2022 +0,0 @@ -/** - * 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.Debug; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.SystemClock; -import android.os.test.TestLooper; -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; - -import com.android.internal.util.StateMachine.LogRec; - -import junit.framework.TestCase; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collection; -import java.util.Iterator; - -/** - * Test for StateMachine. - */ -public class StateMachineTest extends TestCase { - private static final String ENTER = "enter"; - private static final String EXIT = "exit"; - private static final String ON_QUITTING = "ON_QUITTING"; - - private static final int TEST_CMD_1 = 1; - private static final int TEST_CMD_2 = 2; - private static final int TEST_CMD_3 = 3; - private static final int TEST_CMD_4 = 4; - private static final int TEST_CMD_5 = 5; - private static final int TEST_CMD_6 = 6; - - private static final boolean DBG = true; - private static final boolean WAIT_FOR_DEBUGGER = false; - private static final String TAG = "StateMachineTest"; - - private void sleep(int millis) { - try { - Thread.sleep(millis); - } catch(InterruptedException e) { - } - } - - private void dumpLogRecs(StateMachine sm) { - int size = sm.getLogRecSize(); - tlog("size=" + size + " count=" + sm.getLogRecCount()); - for (int i = 0; i < size; i++) { - LogRec lr = sm.getLogRec(i); - tlog(lr.toString()); - } - } - - private void dumpLogRecs(Collection clr) { - int size = clr.size(); - tlog("size=" + size); - for (LogRec lr : clr) { - tlog(lr.toString()); - } - } - - /** - * Tests {@link StateMachine#toString()}. - */ - class StateMachineToStringTest extends StateMachine { - StateMachineToStringTest(String name) { - super(name); - } - } - - class ExampleState extends State { - String mName; - - ExampleState(String name) { - mName = name; - } - - @Override - public String getName() { - return mName; - } - } - - @SmallTest - public void testToStringSucceedsEvenIfMachineHasNoStates() throws Exception { - StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine"); - assertTrue(stateMachine.toString().contains("TestStateMachine")); - } - - @SmallTest - public void testToStringSucceedsEvenIfStateHasNoName() throws Exception { - StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine"); - State exampleState = new ExampleState(null); - stateMachine.addState(exampleState); - stateMachine.setInitialState(exampleState); - stateMachine.start(); - assertTrue(stateMachine.toString().contains("TestStateMachine")); - assertTrue(stateMachine.toString().contains("(null)")); - } - - @SmallTest - public void testToStringIncludesMachineAndStateNames() throws Exception { - StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine"); - State exampleState = new ExampleState("exampleState"); - stateMachine.addState(exampleState); - stateMachine.setInitialState(exampleState); - stateMachine.start(); - assertTrue(stateMachine.toString().contains("TestStateMachine")); - assertTrue(stateMachine.toString().contains("exampleState")); - } - - @SmallTest - public void testToStringDoesNotContainMultipleLines() throws Exception { - StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine"); - State exampleState = new ExampleState("exampleState"); - stateMachine.addState(exampleState); - stateMachine.setInitialState(exampleState); - stateMachine.start(); - assertFalse(stateMachine.toString().contains("\n")); - } - - /** - * Tests {@link StateMachine#quit()}. - */ - class StateMachineQuitTest extends StateMachine { - Collection mLogRecs; - - StateMachineQuitTest(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - } - - @Override - public void onQuitting() { - tlog("onQuitting"); - addLogRec(ON_QUITTING); - mLogRecs = mThisSm.copyLogRecs(); - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - class S1 extends State { - @Override - public void exit() { - tlog("S1.exit"); - addLogRec(EXIT); - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - // Sleep and assume the other messages will be queued up. - case TEST_CMD_1: { - tlog("TEST_CMD_1"); - sleep(500); - quit(); - break; - } - default: { - tlog("default what=" + message.what); - break; - } - } - return HANDLED; - } - } - - private StateMachineQuitTest mThisSm; - private S1 mS1 = new S1(); - } - - @SmallTest - public void testStateMachineQuit() throws Exception { - if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); - - StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest"); - smQuitTest.start(); - if (smQuitTest.isDbg()) tlog("testStateMachineQuit E"); - - synchronized (smQuitTest) { - - // Send 6 message we'll quit on the first but all 6 should be processed before quitting. - for (int i = 1; i <= 6; i++) { - smQuitTest.sendMessage(smQuitTest.obtainMessage(i)); - } - - try { - // wait for the messages to be handled - smQuitTest.wait(); - } catch (InterruptedException e) { - tloge("testStateMachineQuit: exception while waiting " + e.getMessage()); - } - } - - dumpLogRecs(smQuitTest.mLogRecs); - assertEquals(8, smQuitTest.mLogRecs.size()); - - LogRec lr; - Iterator itr = smQuitTest.mLogRecs.iterator(); - for (int i = 1; i <= 6; i++) { - lr = itr.next(); - assertEquals(i, lr.getWhat()); - assertEquals(smQuitTest.mS1, lr.getState()); - assertEquals(smQuitTest.mS1, lr.getOriginalState()); - } - lr = itr.next(); - assertEquals(EXIT, lr.getInfo()); - assertEquals(smQuitTest.mS1, lr.getState()); - - lr = itr.next(); - assertEquals(ON_QUITTING, lr.getInfo()); - - if (smQuitTest.isDbg()) tlog("testStateMachineQuit X"); - } - - /** - * Tests {@link StateMachine#quitNow()} - */ - class StateMachineQuitNowTest extends StateMachine { - public Collection mLogRecs = null; - - StateMachineQuitNowTest(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - } - - @Override - public void onQuitting() { - tlog("onQuitting"); - addLogRec(ON_QUITTING); - // Get a copy of the log records since we're quitting and they will disappear - mLogRecs = mThisSm.copyLogRecs(); - - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - class S1 extends State { - @Override - public void exit() { - tlog("S1.exit"); - addLogRec(EXIT); - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - // Sleep and assume the other messages will be queued up. - case TEST_CMD_1: { - tlog("TEST_CMD_1"); - sleep(500); - quitNow(); - break; - } - default: { - tlog("default what=" + message.what); - break; - } - } - return HANDLED; - } - } - - private StateMachineQuitNowTest mThisSm; - private S1 mS1 = new S1(); - } - - @SmallTest - public void testStateMachineQuitNow() throws Exception { - if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); - - StateMachineQuitNowTest smQuitNowTest = new StateMachineQuitNowTest("smQuitNowTest"); - smQuitNowTest.start(); - if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow E"); - - synchronized (smQuitNowTest) { - - // Send 6 message we'll QuitNow on the first even though - // we send 6 only one will be processed. - for (int i = 1; i <= 6; i++) { - smQuitNowTest.sendMessage(smQuitNowTest.obtainMessage(i)); - } - - try { - // wait for the messages to be handled - smQuitNowTest.wait(); - } catch (InterruptedException e) { - tloge("testStateMachineQuitNow: exception while waiting " + e.getMessage()); - } - } - - tlog("testStateMachineQuiteNow: logRecs=" + smQuitNowTest.mLogRecs); - assertEquals(3, smQuitNowTest.mLogRecs.size()); - - Iterator itr = smQuitNowTest.mLogRecs.iterator(); - LogRec lr = itr.next(); - assertEquals(1, lr.getWhat()); - assertEquals(smQuitNowTest.mS1, lr.getState()); - assertEquals(smQuitNowTest.mS1, lr.getOriginalState()); - - lr = itr.next(); - assertEquals(EXIT, lr.getInfo()); - assertEquals(smQuitNowTest.mS1, lr.getState()); - - lr = itr.next(); - assertEquals(ON_QUITTING, lr.getInfo()); - - if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow X"); - } - - /** - * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}. - */ - class StateMachineQuitNowAfterStartTest extends StateMachine { - Collection mLogRecs; - - StateMachineQuitNowAfterStartTest(String name, Looper looper) { - super(name, looper); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - } - - @Override - public void onQuitting() { - tlog("onQuitting"); - addLogRec(ON_QUITTING); - mLogRecs = mThisSm.copyLogRecs(); - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - class S1 extends State { - @Override - public void enter() { - tlog("S1.enter"); - addLogRec(ENTER); - } - @Override - public void exit() { - tlog("S1.exit"); - addLogRec(EXIT); - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - // Sleep and assume the other messages will be queued up. - case TEST_CMD_1: { - tlog("TEST_CMD_1"); - sleep(500); - break; - } - default: { - tlog("default what=" + message.what); - break; - } - } - return HANDLED; - } - } - - private StateMachineQuitNowAfterStartTest mThisSm; - private S1 mS1 = new S1(); - } - - /** - * When quitNow() is called immediately after start(), the QUIT_CMD gets processed - * before the INIT_CMD. This test ensures that the StateMachine can gracefully handle - * this sequencing of messages (QUIT before INIT). - */ - @SmallTest - public void testStateMachineQuitNowAfterStart() throws Exception { - if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); - - TestLooper testLooper = new TestLooper(); - StateMachineQuitNowAfterStartTest smQuitNowAfterStartTest = - new StateMachineQuitNowAfterStartTest( - "smQuitNowAfterStartTest", testLooper.getLooper()); - smQuitNowAfterStartTest.start(); - smQuitNowAfterStartTest.quitNow(); - if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart E"); - - testLooper.dispatchAll(); - dumpLogRecs(smQuitNowAfterStartTest.mLogRecs); - assertEquals(2, smQuitNowAfterStartTest.mLogRecs.size()); - - LogRec lr; - Iterator itr = smQuitNowAfterStartTest.mLogRecs.iterator(); - lr = itr.next(); - assertEquals(EXIT, lr.getInfo()); - assertEquals(smQuitNowAfterStartTest.mS1, lr.getState()); - - lr = itr.next(); - assertEquals(ON_QUITTING, lr.getInfo()); - - if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart X"); - } - - /** - * Test enter/exit can use transitionTo - */ - class StateMachineEnterExitTransitionToTest extends StateMachine { - - StateMachineEnterExitTransitionToTest(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - addState(mS2); - addState(mS3); - addState(mS4); - - // Set the initial state - setInitialState(mS1); - } - - class S1 extends State { - @Override - public void enter() { - // Test transitions in enter on the initial state work - addLogRec(ENTER); - transitionTo(mS2); - tlog("S1.enter"); - } - @Override - public void exit() { - addLogRec(EXIT); - tlog("S1.exit"); - } - } - - class S2 extends State { - @Override - public void enter() { - addLogRec(ENTER); - tlog("S2.enter"); - } - @Override - public void exit() { - // Test transition in exit work - transitionTo(mS4); - - assertEquals(TEST_CMD_1, getCurrentMessage().what); - addLogRec(EXIT); - - tlog("S2.exit"); - } - @Override - public boolean processMessage(Message message) { - // Start a transition to S3 but it will be - // changed to a transition to S4 in exit - transitionTo(mS3); - tlog("S2.processMessage"); - return HANDLED; - } - } - - class S3 extends State { - @Override - public void enter() { - addLogRec(ENTER); - tlog("S3.enter"); - } - @Override - public void exit() { - addLogRec(EXIT); - tlog("S3.exit"); - } - } - - class S4 extends State { - @Override - public void enter() { - addLogRec(ENTER); - // Test that we can do halting in an enter/exit - transitionToHaltingState(); - tlog("S4.enter"); - } - @Override - public void exit() { - addLogRec(EXIT); - tlog("S4.exit"); - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachineEnterExitTransitionToTest mThisSm; - private S1 mS1 = new S1(); - private S2 mS2 = new S2(); - private S3 mS3 = new S3(); - private S4 mS4 = new S4(); - } - - @SmallTest - public void testStateMachineEnterExitTransitionToTest() throws Exception { - //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); - - StateMachineEnterExitTransitionToTest smEnterExitTransitionToTest = - new StateMachineEnterExitTransitionToTest("smEnterExitTransitionToTest"); - smEnterExitTransitionToTest.start(); - if (smEnterExitTransitionToTest.isDbg()) { - tlog("testStateMachineEnterExitTransitionToTest E"); - } - - synchronized (smEnterExitTransitionToTest) { - smEnterExitTransitionToTest.sendMessage(TEST_CMD_1); - - try { - // wait for the messages to be handled - smEnterExitTransitionToTest.wait(); - } catch (InterruptedException e) { - tloge("testStateMachineEnterExitTransitionToTest: exception while waiting " - + e.getMessage()); - } - } - - dumpLogRecs(smEnterExitTransitionToTest); - - assertEquals(9, smEnterExitTransitionToTest.getLogRecCount()); - LogRec lr; - - lr = smEnterExitTransitionToTest.getLogRec(0); - assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS1, lr.getState()); - - lr = smEnterExitTransitionToTest.getLogRec(1); - assertEquals(EXIT, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS1, lr.getState()); - - lr = smEnterExitTransitionToTest.getLogRec(2); - assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS2, lr.getState()); - - lr = smEnterExitTransitionToTest.getLogRec(3); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(smEnterExitTransitionToTest.mS2, lr.getState()); - assertEquals(smEnterExitTransitionToTest.mS2, lr.getOriginalState()); - assertEquals(smEnterExitTransitionToTest.mS3, lr.getDestState()); - - lr = smEnterExitTransitionToTest.getLogRec(4); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(smEnterExitTransitionToTest.mS2, lr.getState()); - assertEquals(smEnterExitTransitionToTest.mS2, lr.getOriginalState()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - assertEquals(EXIT, lr.getInfo()); - - lr = smEnterExitTransitionToTest.getLogRec(5); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS3, lr.getState()); - assertEquals(smEnterExitTransitionToTest.mS3, lr.getOriginalState()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - - lr = smEnterExitTransitionToTest.getLogRec(6); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(EXIT, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS3, lr.getState()); - assertEquals(smEnterExitTransitionToTest.mS3, lr.getOriginalState()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - - lr = smEnterExitTransitionToTest.getLogRec(7); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getState()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getOriginalState()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - - lr = smEnterExitTransitionToTest.getLogRec(8); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(EXIT, lr.getInfo()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getState()); - assertEquals(smEnterExitTransitionToTest.mS4, lr.getOriginalState()); - - if (smEnterExitTransitionToTest.isDbg()) { - tlog("testStateMachineEnterExitTransitionToTest X"); - } - } - - /** - * Tests that ProcessedMessage works as a circular buffer. - */ - class StateMachine0 extends StateMachine { - StateMachine0(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - setLogRecSize(3); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - } - - class S1 extends State { - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_6) { - transitionToHaltingState(); - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine0 mThisSm; - private S1 mS1 = new S1(); - } - - @SmallTest - public void testStateMachine0() throws Exception { - //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); - - StateMachine0 sm0 = new StateMachine0("sm0"); - sm0.start(); - if (sm0.isDbg()) tlog("testStateMachine0 E"); - - synchronized (sm0) { - // Send 6 messages - for (int i = 1; i <= 6; i++) { - sm0.sendMessage(sm0.obtainMessage(i)); - } - - try { - // wait for the messages to be handled - sm0.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine0: exception while waiting " + e.getMessage()); - } - } - - assertEquals(6, sm0.getLogRecCount()); - assertEquals(3, sm0.getLogRecSize()); - - dumpLogRecs(sm0); - - LogRec lr; - lr = sm0.getLogRec(0); - assertEquals(TEST_CMD_4, lr.getWhat()); - assertEquals(sm0.mS1, lr.getState()); - assertEquals(sm0.mS1, lr.getOriginalState()); - - lr = sm0.getLogRec(1); - assertEquals(TEST_CMD_5, lr.getWhat()); - assertEquals(sm0.mS1, lr.getState()); - assertEquals(sm0.mS1, lr.getOriginalState()); - - lr = sm0.getLogRec(2); - assertEquals(TEST_CMD_6, lr.getWhat()); - assertEquals(sm0.mS1, lr.getState()); - assertEquals(sm0.mS1, lr.getOriginalState()); - - if (sm0.isDbg()) tlog("testStateMachine0 X"); - } - - /** - * This tests enter/exit and transitions to the same state. - * The state machine has one state, it receives two messages - * in state mS1. With the first message it transitions to - * itself which causes it to be exited and reentered. - */ - class StateMachine1 extends StateMachine { - StateMachine1(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - if (DBG) tlog("StateMachine1: ctor X"); - } - - class S1 extends State { - @Override - public void enter() { - mEnterCount++; - } - @Override - public void exit() { - mExitCount++; - } - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_1) { - assertEquals(1, mEnterCount); - assertEquals(0, mExitCount); - transitionTo(mS1); - } else if (message.what == TEST_CMD_2) { - assertEquals(2, mEnterCount); - assertEquals(1, mExitCount); - transitionToHaltingState(); - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine1 mThisSm; - private S1 mS1 = new S1(); - - private int mEnterCount; - private int mExitCount; - } - - @MediumTest - public void testStateMachine1() throws Exception { - StateMachine1 sm1 = new StateMachine1("sm1"); - sm1.start(); - if (sm1.isDbg()) tlog("testStateMachine1 E"); - - synchronized (sm1) { - // Send two messages - sm1.sendMessage(TEST_CMD_1); - sm1.sendMessage(TEST_CMD_2); - - try { - // wait for the messages to be handled - sm1.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine1: exception while waiting " + e.getMessage()); - } - } - - assertEquals(2, sm1.mEnterCount); - assertEquals(2, sm1.mExitCount); - - assertEquals(2, sm1.getLogRecSize()); - - LogRec lr; - lr = sm1.getLogRec(0); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(sm1.mS1, lr.getState()); - assertEquals(sm1.mS1, lr.getOriginalState()); - - lr = sm1.getLogRec(1); - assertEquals(TEST_CMD_2, lr.getWhat()); - assertEquals(sm1.mS1, lr.getState()); - assertEquals(sm1.mS1, lr.getOriginalState()); - - assertEquals(2, sm1.mEnterCount); - assertEquals(2, sm1.mExitCount); - - if (sm1.isDbg()) tlog("testStateMachine1 X"); - } - - /** - * Test deferring messages and states with no parents. The state machine - * has two states, it receives two messages in state mS1 deferring them - * until what == TEST_CMD_2 and then transitions to state mS2. State - * mS2 then receives both of the deferred messages first TEST_CMD_1 and - * then TEST_CMD_2. - */ - class StateMachine2 extends StateMachine { - StateMachine2(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup the hierarchy - addState(mS1); - addState(mS2); - - // Set the initial state - setInitialState(mS1); - if (DBG) tlog("StateMachine2: ctor X"); - } - - class S1 extends State { - @Override - public void enter() { - mDidEnter = true; - } - @Override - public void exit() { - mDidExit = true; - } - @Override - public boolean processMessage(Message message) { - deferMessage(message); - if (message.what == TEST_CMD_2) { - transitionTo(mS2); - } - return HANDLED; - } - } - - class S2 extends State { - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_2) { - transitionToHaltingState(); - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine2 mThisSm; - private S1 mS1 = new S1(); - private S2 mS2 = new S2(); - - private boolean mDidEnter = false; - private boolean mDidExit = false; - } - - @MediumTest - public void testStateMachine2() throws Exception { - StateMachine2 sm2 = new StateMachine2("sm2"); - sm2.start(); - if (sm2.isDbg()) tlog("testStateMachine2 E"); - - synchronized (sm2) { - // Send two messages - sm2.sendMessage(TEST_CMD_1); - sm2.sendMessage(TEST_CMD_2); - - try { - // wait for the messages to be handled - sm2.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine2: exception while waiting " + e.getMessage()); - } - } - - assertEquals(4, sm2.getLogRecSize()); - - LogRec lr; - lr = sm2.getLogRec(0); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(sm2.mS1, lr.getState()); - - lr = sm2.getLogRec(1); - assertEquals(TEST_CMD_2, lr.getWhat()); - assertEquals(sm2.mS1, lr.getState()); - - lr = sm2.getLogRec(2); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(sm2.mS2, lr.getState()); - - lr = sm2.getLogRec(3); - assertEquals(TEST_CMD_2, lr.getWhat()); - assertEquals(sm2.mS2, lr.getState()); - - assertTrue(sm2.mDidEnter); - assertTrue(sm2.mDidExit); - - if (sm2.isDbg()) tlog("testStateMachine2 X"); - } - - /** - * Test that unhandled messages in a child are handled by the parent. - * When TEST_CMD_2 is received. - */ - class StateMachine3 extends StateMachine { - StateMachine3(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup the simplest hierarchy of two states - // mParentState and mChildState. - // (Use indentation to help visualize hierarchy) - addState(mParentState); - addState(mChildState, mParentState); - - // Set the initial state will be the child - setInitialState(mChildState); - if (DBG) tlog("StateMachine3: ctor X"); - } - - class ParentState extends State { - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_2) { - transitionToHaltingState(); - } - return HANDLED; - } - } - - class ChildState extends State { - @Override - public boolean processMessage(Message message) { - return NOT_HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine3 mThisSm; - private ParentState mParentState = new ParentState(); - private ChildState mChildState = new ChildState(); - } - - @MediumTest - public void testStateMachine3() throws Exception { - StateMachine3 sm3 = new StateMachine3("sm3"); - sm3.start(); - if (sm3.isDbg()) tlog("testStateMachine3 E"); - - synchronized (sm3) { - // Send two messages - sm3.sendMessage(TEST_CMD_1); - sm3.sendMessage(TEST_CMD_2); - - try { - // wait for the messages to be handled - sm3.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine3: exception while waiting " + e.getMessage()); - } - } - - assertEquals(2, sm3.getLogRecSize()); - - LogRec lr; - lr = sm3.getLogRec(0); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(sm3.mParentState, lr.getState()); - assertEquals(sm3.mChildState, lr.getOriginalState()); - - lr = sm3.getLogRec(1); - assertEquals(TEST_CMD_2, lr.getWhat()); - assertEquals(sm3.mParentState, lr.getState()); - assertEquals(sm3.mChildState, lr.getOriginalState()); - - if (sm3.isDbg()) tlog("testStateMachine3 X"); - } - - /** - * Test a hierarchy of 3 states a parent and two children - * with transition from child 1 to child 2 and child 2 - * lets the parent handle the messages. - */ - class StateMachine4 extends StateMachine { - StateMachine4(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup a hierarchy of three states - // mParentState, mChildState1 & mChildState2 - // (Use indentation to help visualize hierarchy) - addState(mParentState); - addState(mChildState1, mParentState); - addState(mChildState2, mParentState); - - // Set the initial state will be child 1 - setInitialState(mChildState1); - if (DBG) tlog("StateMachine4: ctor X"); - } - - class ParentState extends State { - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_2) { - transitionToHaltingState(); - } - return HANDLED; - } - } - - class ChildState1 extends State { - @Override - public boolean processMessage(Message message) { - transitionTo(mChildState2); - return HANDLED; - } - } - - class ChildState2 extends State { - @Override - public boolean processMessage(Message message) { - return NOT_HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine4 mThisSm; - private ParentState mParentState = new ParentState(); - private ChildState1 mChildState1 = new ChildState1(); - private ChildState2 mChildState2 = new ChildState2(); - } - - @MediumTest - public void testStateMachine4() throws Exception { - StateMachine4 sm4 = new StateMachine4("sm4"); - sm4.start(); - if (sm4.isDbg()) tlog("testStateMachine4 E"); - - synchronized (sm4) { - // Send two messages - sm4.sendMessage(TEST_CMD_1); - sm4.sendMessage(TEST_CMD_2); - - try { - // wait for the messages to be handled - sm4.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine4: exception while waiting " + e.getMessage()); - } - } - - - assertEquals(2, sm4.getLogRecSize()); - - LogRec lr; - lr = sm4.getLogRec(0); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(sm4.mChildState1, lr.getState()); - assertEquals(sm4.mChildState1, lr.getOriginalState()); - - lr = sm4.getLogRec(1); - assertEquals(TEST_CMD_2, lr.getWhat()); - assertEquals(sm4.mParentState, lr.getState()); - assertEquals(sm4.mChildState2, lr.getOriginalState()); - - if (sm4.isDbg()) tlog("testStateMachine4 X"); - } - - /** - * Test transition from one child to another of a "complex" - * hierarchy with two parents and multiple children. - */ - class StateMachine5 extends StateMachine { - StateMachine5(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup a hierarchy with two parents and some children. - // (Use indentation to help visualize hierarchy) - addState(mParentState1); - addState(mChildState1, mParentState1); - addState(mChildState2, mParentState1); - - addState(mParentState2); - addState(mChildState3, mParentState2); - addState(mChildState4, mParentState2); - addState(mChildState5, mChildState4); - - // Set the initial state will be the child - setInitialState(mChildState1); - if (DBG) tlog("StateMachine5: ctor X"); - } - - class ParentState1 extends State { - @Override - public void enter() { - mParentState1EnterCount += 1; - } - @Override - public void exit() { - mParentState1ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - return HANDLED; - } - } - - class ChildState1 extends State { - @Override - public void enter() { - mChildState1EnterCount += 1; - } - @Override - public void exit() { - mChildState1ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - assertEquals(1, mParentState1EnterCount); - assertEquals(0, mParentState1ExitCount); - assertEquals(1, mChildState1EnterCount); - assertEquals(0, mChildState1ExitCount); - assertEquals(0, mChildState2EnterCount); - assertEquals(0, mChildState2ExitCount); - assertEquals(0, mParentState2EnterCount); - assertEquals(0, mParentState2ExitCount); - assertEquals(0, mChildState3EnterCount); - assertEquals(0, mChildState3ExitCount); - assertEquals(0, mChildState4EnterCount); - assertEquals(0, mChildState4ExitCount); - assertEquals(0, mChildState5EnterCount); - assertEquals(0, mChildState5ExitCount); - - transitionTo(mChildState2); - return HANDLED; - } - } - - class ChildState2 extends State { - @Override - public void enter() { - mChildState2EnterCount += 1; - } - @Override - public void exit() { - mChildState2ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - assertEquals(1, mParentState1EnterCount); - assertEquals(0, mParentState1ExitCount); - assertEquals(1, mChildState1EnterCount); - assertEquals(1, mChildState1ExitCount); - assertEquals(1, mChildState2EnterCount); - assertEquals(0, mChildState2ExitCount); - assertEquals(0, mParentState2EnterCount); - assertEquals(0, mParentState2ExitCount); - assertEquals(0, mChildState3EnterCount); - assertEquals(0, mChildState3ExitCount); - assertEquals(0, mChildState4EnterCount); - assertEquals(0, mChildState4ExitCount); - assertEquals(0, mChildState5EnterCount); - assertEquals(0, mChildState5ExitCount); - - transitionTo(mChildState5); - return HANDLED; - } - } - - class ParentState2 extends State { - @Override - public void enter() { - mParentState2EnterCount += 1; - } - @Override - public void exit() { - mParentState2ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - assertEquals(1, mParentState1EnterCount); - assertEquals(1, mParentState1ExitCount); - assertEquals(1, mChildState1EnterCount); - assertEquals(1, mChildState1ExitCount); - assertEquals(1, mChildState2EnterCount); - assertEquals(1, mChildState2ExitCount); - assertEquals(2, mParentState2EnterCount); - assertEquals(1, mParentState2ExitCount); - assertEquals(1, mChildState3EnterCount); - assertEquals(1, mChildState3ExitCount); - assertEquals(2, mChildState4EnterCount); - assertEquals(2, mChildState4ExitCount); - assertEquals(1, mChildState5EnterCount); - assertEquals(1, mChildState5ExitCount); - - transitionToHaltingState(); - return HANDLED; - } - } - - class ChildState3 extends State { - @Override - public void enter() { - mChildState3EnterCount += 1; - } - @Override - public void exit() { - mChildState3ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - assertEquals(1, mParentState1EnterCount); - assertEquals(1, mParentState1ExitCount); - assertEquals(1, mChildState1EnterCount); - assertEquals(1, mChildState1ExitCount); - assertEquals(1, mChildState2EnterCount); - assertEquals(1, mChildState2ExitCount); - assertEquals(1, mParentState2EnterCount); - assertEquals(0, mParentState2ExitCount); - assertEquals(1, mChildState3EnterCount); - assertEquals(0, mChildState3ExitCount); - assertEquals(1, mChildState4EnterCount); - assertEquals(1, mChildState4ExitCount); - assertEquals(1, mChildState5EnterCount); - assertEquals(1, mChildState5ExitCount); - - transitionTo(mChildState4); - return HANDLED; - } - } - - class ChildState4 extends State { - @Override - public void enter() { - mChildState4EnterCount += 1; - } - @Override - public void exit() { - mChildState4ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - assertEquals(1, mParentState1EnterCount); - assertEquals(1, mParentState1ExitCount); - assertEquals(1, mChildState1EnterCount); - assertEquals(1, mChildState1ExitCount); - assertEquals(1, mChildState2EnterCount); - assertEquals(1, mChildState2ExitCount); - assertEquals(1, mParentState2EnterCount); - assertEquals(0, mParentState2ExitCount); - assertEquals(1, mChildState3EnterCount); - assertEquals(1, mChildState3ExitCount); - assertEquals(2, mChildState4EnterCount); - assertEquals(1, mChildState4ExitCount); - assertEquals(1, mChildState5EnterCount); - assertEquals(1, mChildState5ExitCount); - - transitionTo(mParentState2); - return HANDLED; - } - } - - class ChildState5 extends State { - @Override - public void enter() { - mChildState5EnterCount += 1; - } - @Override - public void exit() { - mChildState5ExitCount += 1; - } - @Override - public boolean processMessage(Message message) { - assertEquals(1, mParentState1EnterCount); - assertEquals(1, mParentState1ExitCount); - assertEquals(1, mChildState1EnterCount); - assertEquals(1, mChildState1ExitCount); - assertEquals(1, mChildState2EnterCount); - assertEquals(1, mChildState2ExitCount); - assertEquals(1, mParentState2EnterCount); - assertEquals(0, mParentState2ExitCount); - assertEquals(0, mChildState3EnterCount); - assertEquals(0, mChildState3ExitCount); - assertEquals(1, mChildState4EnterCount); - assertEquals(0, mChildState4ExitCount); - assertEquals(1, mChildState5EnterCount); - assertEquals(0, mChildState5ExitCount); - - transitionTo(mChildState3); - return HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine5 mThisSm; - private ParentState1 mParentState1 = new ParentState1(); - private ChildState1 mChildState1 = new ChildState1(); - private ChildState2 mChildState2 = new ChildState2(); - private ParentState2 mParentState2 = new ParentState2(); - private ChildState3 mChildState3 = new ChildState3(); - private ChildState4 mChildState4 = new ChildState4(); - private ChildState5 mChildState5 = new ChildState5(); - - private int mParentState1EnterCount = 0; - private int mParentState1ExitCount = 0; - private int mChildState1EnterCount = 0; - private int mChildState1ExitCount = 0; - private int mChildState2EnterCount = 0; - private int mChildState2ExitCount = 0; - private int mParentState2EnterCount = 0; - private int mParentState2ExitCount = 0; - private int mChildState3EnterCount = 0; - private int mChildState3ExitCount = 0; - private int mChildState4EnterCount = 0; - private int mChildState4ExitCount = 0; - private int mChildState5EnterCount = 0; - private int mChildState5ExitCount = 0; - } - - @MediumTest - public void testStateMachine5() throws Exception { - StateMachine5 sm5 = new StateMachine5("sm5"); - sm5.start(); - if (sm5.isDbg()) tlog("testStateMachine5 E"); - - synchronized (sm5) { - // Send 6 messages - sm5.sendMessage(TEST_CMD_1); - sm5.sendMessage(TEST_CMD_2); - sm5.sendMessage(TEST_CMD_3); - sm5.sendMessage(TEST_CMD_4); - sm5.sendMessage(TEST_CMD_5); - sm5.sendMessage(TEST_CMD_6); - - try { - // wait for the messages to be handled - sm5.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine5: exception while waiting " + e.getMessage()); - } - } - - - assertEquals(6, sm5.getLogRecSize()); - - assertEquals(1, sm5.mParentState1EnterCount); - assertEquals(1, sm5.mParentState1ExitCount); - assertEquals(1, sm5.mChildState1EnterCount); - assertEquals(1, sm5.mChildState1ExitCount); - assertEquals(1, sm5.mChildState2EnterCount); - assertEquals(1, sm5.mChildState2ExitCount); - assertEquals(2, sm5.mParentState2EnterCount); - assertEquals(2, sm5.mParentState2ExitCount); - assertEquals(1, sm5.mChildState3EnterCount); - assertEquals(1, sm5.mChildState3ExitCount); - assertEquals(2, sm5.mChildState4EnterCount); - assertEquals(2, sm5.mChildState4ExitCount); - assertEquals(1, sm5.mChildState5EnterCount); - assertEquals(1, sm5.mChildState5ExitCount); - - LogRec lr; - lr = sm5.getLogRec(0); - assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(sm5.mChildState1, lr.getState()); - assertEquals(sm5.mChildState1, lr.getOriginalState()); - - lr = sm5.getLogRec(1); - assertEquals(TEST_CMD_2, lr.getWhat()); - assertEquals(sm5.mChildState2, lr.getState()); - assertEquals(sm5.mChildState2, lr.getOriginalState()); - - lr = sm5.getLogRec(2); - assertEquals(TEST_CMD_3, lr.getWhat()); - assertEquals(sm5.mChildState5, lr.getState()); - assertEquals(sm5.mChildState5, lr.getOriginalState()); - - lr = sm5.getLogRec(3); - assertEquals(TEST_CMD_4, lr.getWhat()); - assertEquals(sm5.mChildState3, lr.getState()); - assertEquals(sm5.mChildState3, lr.getOriginalState()); - - lr = sm5.getLogRec(4); - assertEquals(TEST_CMD_5, lr.getWhat()); - assertEquals(sm5.mChildState4, lr.getState()); - assertEquals(sm5.mChildState4, lr.getOriginalState()); - - lr = sm5.getLogRec(5); - assertEquals(TEST_CMD_6, lr.getWhat()); - assertEquals(sm5.mParentState2, lr.getState()); - assertEquals(sm5.mParentState2, lr.getOriginalState()); - - if (sm5.isDbg()) tlog("testStateMachine5 X"); - } - - /** - * Test that the initial state enter is invoked immediately - * after construction and before any other messages arrive and that - * sendMessageDelayed works. - */ - class StateMachine6 extends StateMachine { - StateMachine6(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - if (DBG) tlog("StateMachine6: ctor X"); - } - - class S1 extends State { - @Override - public void enter() { - sendMessage(TEST_CMD_1); - } - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_1) { - mArrivalTimeMsg1 = SystemClock.elapsedRealtime(); - } else if (message.what == TEST_CMD_2) { - mArrivalTimeMsg2 = SystemClock.elapsedRealtime(); - transitionToHaltingState(); - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine6 mThisSm; - private S1 mS1 = new S1(); - - private long mArrivalTimeMsg1; - private long mArrivalTimeMsg2; - } - - @MediumTest - public void testStateMachine6() throws Exception { - final int DELAY_TIME = 250; - final int DELAY_FUDGE = 20; - - StateMachine6 sm6 = new StateMachine6("sm6"); - sm6.start(); - if (sm6.isDbg()) tlog("testStateMachine6 E"); - - synchronized (sm6) { - // Send a message - sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME); - - try { - // wait for the messages to be handled - sm6.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine6: exception while waiting " + e.getMessage()); - } - } - - /** - * TEST_CMD_1 was sent in enter and must always have been processed - * immediately after construction and hence the arrival time difference - * should always >= to the DELAY_TIME - */ - long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1; - long expectedDelay = DELAY_TIME - DELAY_FUDGE; - if (sm6.isDbg()) tlog("testStateMachine6: expect " + arrivalTimeDiff - + " >= " + expectedDelay); - assertTrue(arrivalTimeDiff >= expectedDelay); - - if (sm6.isDbg()) tlog("testStateMachine6 X"); - } - - /** - * Test that enter is invoked immediately after exit. This validates - * that enter can be used to send a watch dog message for its state. - */ - class StateMachine7 extends StateMachine { - private final int SM7_DELAY_TIME = 250; - - StateMachine7(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - addState(mS2); - - // Set the initial state - setInitialState(mS1); - if (DBG) tlog("StateMachine7: ctor X"); - } - - class S1 extends State { - @Override - public void exit() { - sendMessage(TEST_CMD_2); - } - @Override - public boolean processMessage(Message message) { - transitionTo(mS2); - return HANDLED; - } - } - - class S2 extends State { - @Override - public void enter() { - // Send a delayed message as a watch dog - sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME); - } - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_2) { - mMsgCount += 1; - mArrivalTimeMsg2 = SystemClock.elapsedRealtime(); - } else if (message.what == TEST_CMD_3) { - mMsgCount += 1; - mArrivalTimeMsg3 = SystemClock.elapsedRealtime(); - } - - if (mMsgCount == 2) { - transitionToHaltingState(); - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachine7 mThisSm; - private S1 mS1 = new S1(); - private S2 mS2 = new S2(); - - private int mMsgCount = 0; - private long mArrivalTimeMsg2; - private long mArrivalTimeMsg3; - } - - @MediumTest - public void testStateMachine7() throws Exception { - final int SM7_DELAY_FUDGE = 20; - - StateMachine7 sm7 = new StateMachine7("sm7"); - sm7.start(); - if (sm7.isDbg()) tlog("testStateMachine7 E"); - - synchronized (sm7) { - // Send a message - sm7.sendMessage(TEST_CMD_1); - - try { - // wait for the messages to be handled - sm7.wait(); - } catch (InterruptedException e) { - tloge("testStateMachine7: exception while waiting " + e.getMessage()); - } - } - - /** - * TEST_CMD_3 was sent in S2.enter with a delay and must always have been - * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2 - * without a delay the arrival time difference should always >= to SM7_DELAY_TIME. - */ - long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2; - long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE; - if (sm7.isDbg()) tlog("testStateMachine7: expect " + arrivalTimeDiff - + " >= " + expectedDelay); - assertTrue(arrivalTimeDiff >= expectedDelay); - - if (sm7.isDbg()) tlog("testStateMachine7 X"); - } - - /** - * Test unhandledMessage. - */ - class StateMachineUnhandledMessage extends StateMachine { - StateMachineUnhandledMessage(String name) { - super(name); - mThisSm = this; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - } - @Override - public void unhandledMessage(Message message) { - mUnhandledMessageCount += 1; - } - - class S1 extends State { - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_2) { - transitionToHaltingState(); - } - return NOT_HANDLED; - } - } - - @Override - protected void onHalting() { - synchronized (mThisSm) { - mThisSm.notifyAll(); - } - } - - private StateMachineUnhandledMessage mThisSm; - private int mUnhandledMessageCount; - private S1 mS1 = new S1(); - } - - @SmallTest - public void testStateMachineUnhandledMessage() throws Exception { - - StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("smUnhandledMessage"); - sm.start(); - if (sm.isDbg()) tlog("testStateMachineUnhandledMessage E"); - - synchronized (sm) { - // Send 2 messages - for (int i = 1; i <= 2; i++) { - sm.sendMessage(i); - } - - try { - // wait for the messages to be handled - sm.wait(); - } catch (InterruptedException e) { - tloge("testStateMachineUnhandledMessage: exception while waiting " - + e.getMessage()); - } - } - - assertEquals(2, sm.getLogRecSize()); - assertEquals(2, sm.mUnhandledMessageCount); - - if (sm.isDbg()) tlog("testStateMachineUnhandledMessage X"); - } - - /** - * Test state machines sharing the same thread/looper. Multiple instances - * of the same state machine will be created. They will all share the - * same thread and thus each can update sharedCounter which - * will be used to notify testStateMachineSharedThread that the test is - * complete. - */ - class StateMachineSharedThread extends StateMachine { - StateMachineSharedThread(String name, Looper looper, int maxCount) { - super(name, looper); - mMaxCount = maxCount; - setDbg(DBG); - - // Setup state machine with 1 state - addState(mS1); - - // Set the initial state - setInitialState(mS1); - } - - class S1 extends State { - @Override - public boolean processMessage(Message message) { - if (message.what == TEST_CMD_4) { - transitionToHaltingState(); - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - // Update the shared counter, which is OK since all state - // machines are using the same thread. - sharedCounter += 1; - if (sharedCounter == mMaxCount) { - synchronized (waitObject) { - waitObject.notifyAll(); - } - } - } - - private int mMaxCount; - private S1 mS1 = new S1(); - } - private static int sharedCounter = 0; - private static Object waitObject = new Object(); - - @MediumTest - public void testStateMachineSharedThread() throws Exception { - if (DBG) tlog("testStateMachineSharedThread E"); - - // Create and start the handler thread - HandlerThread smThread = new HandlerThread("testStateMachineSharedThread"); - smThread.start(); - - // Create the state machines - StateMachineSharedThread sms[] = new StateMachineSharedThread[10]; - for (int i = 0; i < sms.length; i++) { - sms[i] = new StateMachineSharedThread("smSharedThread", - smThread.getLooper(), sms.length); - sms[i].start(); - } - - synchronized (waitObject) { - // Send messages to each of the state machines - for (StateMachineSharedThread sm : sms) { - for (int i = 1; i <= 4; i++) { - sm.sendMessage(i); - } - } - - // Wait for the last state machine to notify its done - try { - waitObject.wait(); - } catch (InterruptedException e) { - tloge("testStateMachineSharedThread: exception while waiting " - + e.getMessage()); - } - } - - for (StateMachineSharedThread sm : sms) { - assertEquals(4, sm.getLogRecCount()); - for (int i = 0; i < sm.getLogRecSize(); i++) { - LogRec lr = sm.getLogRec(i); - assertEquals(i+1, lr.getWhat()); - assertEquals(sm.mS1, lr.getState()); - assertEquals(sm.mS1, lr.getOriginalState()); - } - } - - if (DBG) tlog("testStateMachineSharedThread X"); - } - - static class Hsm1 extends StateMachine { - private static final String HSM1_TAG = "hsm1"; - - public static final int CMD_1 = 1; - public static final int CMD_2 = 2; - public static final int CMD_3 = 3; - public static final int CMD_4 = 4; - public static final int CMD_5 = 5; - - public static Hsm1 makeHsm1() { - Log.d(HSM1_TAG, "makeHsm1 E"); - Hsm1 sm = new Hsm1(HSM1_TAG); - sm.start(); - Log.d(HSM1_TAG, "makeHsm1 X"); - return sm; - } - - Hsm1(String name) { - super(name); - tlog("ctor E"); - - // Add states, use indentation to show hierarchy - addState(mP1); - addState(mS1, mP1); - addState(mS2, mP1); - addState(mP2); - - // Set the initial state - setInitialState(mS1); - tlog("ctor X"); - } - - class P1 extends State { - @Override - public void enter() { - tlog("P1.enter"); - } - @Override - public void exit() { - tlog("P1.exit"); - } - @Override - public boolean processMessage(Message message) { - boolean retVal; - tlog("P1.processMessage what=" + message.what); - switch(message.what) { - case CMD_2: - // CMD_2 will arrive in mS2 before CMD_3 - sendMessage(CMD_3); - deferMessage(message); - transitionTo(mS2); - retVal = true; - break; - default: - // Any message we don't understand in this state invokes unhandledMessage - retVal = false; - break; - } - return retVal; - } - } - - class S1 extends State { - @Override - public void enter() { - tlog("S1.enter"); - } - @Override - public void exit() { - tlog("S1.exit"); - } - @Override - public boolean processMessage(Message message) { - tlog("S1.processMessage what=" + message.what); - if (message.what == CMD_1) { - // Transition to ourself to show that enter/exit is called - transitionTo(mS1); - return HANDLED; - } else { - // Let parent process all other messages - return NOT_HANDLED; - } - } - } - - class S2 extends State { - @Override - public void enter() { - tlog("S2.enter"); - } - @Override - public void exit() { - tlog("S2.exit"); - } - @Override - public boolean processMessage(Message message) { - boolean retVal; - tlog("S2.processMessage what=" + message.what); - switch(message.what) { - case(CMD_2): - sendMessage(CMD_4); - retVal = true; - break; - case(CMD_3): - deferMessage(message); - transitionTo(mP2); - retVal = true; - break; - default: - retVal = false; - break; - } - return retVal; - } - } - - class P2 extends State { - @Override - public void enter() { - tlog("P2.enter"); - sendMessage(CMD_5); - } - @Override - public void exit() { - tlog("P2.exit"); - } - @Override - public boolean processMessage(Message message) { - tlog("P2.processMessage what=" + message.what); - switch(message.what) { - case(CMD_3): - break; - case(CMD_4): - break; - case(CMD_5): - transitionToHaltingState(); - break; - } - return HANDLED; - } - } - - @Override - protected void onHalting() { - tlog("halting"); - synchronized (this) { - this.notifyAll(); - } - } - - P1 mP1 = new P1(); - S1 mS1 = new S1(); - S2 mS2 = new S2(); - P2 mP2 = new P2(); - } - - @MediumTest - public void testHsm1() throws Exception { - if (DBG) tlog("testHsm1 E"); - - Hsm1 sm = Hsm1.makeHsm1(); - - // Send messages - sm.sendMessage(Hsm1.CMD_1); - sm.sendMessage(Hsm1.CMD_2); - - synchronized (sm) { - // Wait for the last state machine to notify its done - try { - sm.wait(); - } catch (InterruptedException e) { - tloge("testHsm1: exception while waiting " + e.getMessage()); - } - } - - dumpLogRecs(sm); - - assertEquals(7, sm.getLogRecCount()); - - LogRec lr = sm.getLogRec(0); - assertEquals(Hsm1.CMD_1, lr.getWhat()); - assertEquals(sm.mS1, lr.getState()); - assertEquals(sm.mS1, lr.getOriginalState()); - - lr = sm.getLogRec(1); - assertEquals(Hsm1.CMD_2, lr.getWhat()); - assertEquals(sm.mP1, lr.getState()); - assertEquals(sm.mS1, lr.getOriginalState()); - - lr = sm.getLogRec(2); - assertEquals(Hsm1.CMD_2, lr.getWhat()); - assertEquals(sm.mS2, lr.getState()); - assertEquals(sm.mS2, lr.getOriginalState()); - - lr = sm.getLogRec(3); - assertEquals(Hsm1.CMD_3, lr.getWhat()); - assertEquals(sm.mS2, lr.getState()); - assertEquals(sm.mS2, lr.getOriginalState()); - - lr = sm.getLogRec(4); - assertEquals(Hsm1.CMD_3, lr.getWhat()); - assertEquals(sm.mP2, lr.getState()); - assertEquals(sm.mP2, lr.getOriginalState()); - - lr = sm.getLogRec(5); - assertEquals(Hsm1.CMD_4, lr.getWhat()); - assertEquals(sm.mP2, lr.getState()); - assertEquals(sm.mP2, lr.getOriginalState()); - - lr = sm.getLogRec(6); - assertEquals(Hsm1.CMD_5, lr.getWhat()); - assertEquals(sm.mP2, lr.getState()); - assertEquals(sm.mP2, lr.getOriginalState()); - - if (DBG) tlog("testStateMachineSharedThread X"); - } - - private static void tlog(String s) { - Log.d(TAG, s); - } - - private static void tloge(String s) { - Log.e(TAG, s); - } - - public void testDumpDoesNotThrowNpeAfterQuit() { - final Hsm1 sm = Hsm1.makeHsm1(); - sm.quitNow(); - final StringWriter stringWriter = new StringWriter(); - final PrintWriter printWriter = new PrintWriter(stringWriter); - sm.dump(null, printWriter, new String[0]); - } -}