diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java index f2b0cfbd5a..5a984c7414 100644 --- a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java +++ b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java @@ -18,11 +18,15 @@ package com.android.networkstack.tethering.util; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.util.State; import java.util.List; +import java.util.Objects; +import java.util.Set; /** * An implementation of a state machine, meant to be called synchronously. @@ -35,6 +39,7 @@ public class SyncStateMachine { @NonNull private final String mName; @NonNull private final Thread mMyThread; private final boolean mDbg; + private final ArrayMap mStateInfo = new ArrayMap<>(); // mCurrentState is the current state. mDestState is the target state that mCurrentState will // transition to. The value of mDestState can be changed when a state processes a message and @@ -91,9 +96,9 @@ public class SyncStateMachine { } /** - * Add all of states to the state machine. Different StateInfos which have same state but have - * different parents are not allowed. A state can not have multiple parent states. - * This can only be called once either from mMyThread or before mMyThread started. + * Add all of states to the state machine. Different StateInfos which have same state are not + * allowed. In other words, a state can not have multiple parent states. #addAllStates can + * only be called once either from mMyThread or before mMyThread started. */ public final void addAllStates(@NonNull final List stateInfos) { ensureCorrectOrNotStartedThread(); @@ -101,13 +106,39 @@ public class SyncStateMachine { if (mCurrentState != null) { throw new IllegalStateException("State only can be added before started"); } + + if (stateInfos.isEmpty()) throw new IllegalStateException("Empty state is not allowed"); + + if (!mStateInfo.isEmpty()) throw new IllegalStateException("States are already configured"); + + final Set usedClasses = new ArraySet<>(); + for (final StateInfo info : stateInfos) { + Objects.requireNonNull(info.state); + if (!usedClasses.add(info.state.getClass())) { + throw new IllegalStateException("Adding the same state multiple times in a state " + + "machine is forbidden because it tends to be confusing; it can be done " + + "with anonymous subclasses but consider carefully whether you want to " + + "use a single state or other alternatives instead."); + } + + mStateInfo.put(info.state, info); + } + + // Check whether all of parent states indicated from StateInfo are added. + for (final StateInfo info : stateInfos) { + if (info.parent != null) ensureExistingState(info.parent); + } } /** * Start the state machine. The initial state can't be child state. + * + * @param initialState the first state of this machine. The state must be exact state object + * setting up by {@link #addAllStates}, not a copy of it. */ public final void start(@NonNull final State initialState) { ensureCorrectThread(); + ensureExistingState(initialState); mCurrentState = initialState; mDestState = initialState; @@ -127,11 +158,13 @@ public class SyncStateMachine { * This function can NOT be called inside the State enter and exit function. The transition * target is always defined and can never be changed mid-way of state transition. * - * @param destState will be the state to transition to. + * @param destState will be the state to transition to. The state must be the same instance set + * up by {@link #addAllStates}, not a copy of it. */ public final void transitionTo(@NonNull final State destState) { if (mDbg) Log.d(mName, "transitionTo " + destState); ensureCorrectThread(); + ensureExistingState(destState); if (mDestState == mCurrentState) { mDestState = destState; @@ -151,4 +184,8 @@ public class SyncStateMachine { ensureCorrectThread(); } + + private void ensureExistingState(@NonNull final State state) { + if (!mStateInfo.containsKey(state)) throw new IllegalStateException("Invalid state"); + } }