1 /** 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.net.module.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Message; 22 import android.util.ArrayMap; 23 import android.util.ArraySet; 24 import android.util.Log; 25 26 import com.android.internal.util.State; 27 28 import java.util.ArrayDeque; 29 import java.util.List; 30 import java.util.Objects; 31 import java.util.Set; 32 33 /** 34 * An implementation of a state machine, meant to be called synchronously. 35 * 36 * This class implements a finite state automaton based on the same State 37 * class as StateMachine. 38 * All methods of this class must be called on only one thread. 39 */ 40 public class SyncStateMachine { 41 @NonNull private final String mName; 42 @NonNull private final Thread mMyThread; 43 private final boolean mDbg; 44 private final ArrayMap<State, StateInfo> mStateInfo = new ArrayMap<>(); 45 46 // mCurrentState is the current state. mDestState is the target state that mCurrentState will 47 // transition to. The value of mDestState can be changed when a state processes a message and 48 // calls #transitionTo, but it cannot be changed during the state transition. When the state 49 // transition is complete, mDestState will be set to mCurrentState. Both mCurrentState and 50 // mDestState only be null before state machine starts and must only be touched on mMyThread. 51 @Nullable private State mCurrentState; 52 @Nullable private State mDestState; 53 private final ArrayDeque<Message> mSelfMsgQueue = new ArrayDeque<Message>(); 54 55 // MIN_VALUE means not currently processing any message. 56 private int mCurrentlyProcessing = Integer.MIN_VALUE; 57 // Indicates whether automaton can send self message. Self messages can only be sent by 58 // automaton from State#enter, State#exit, or State#processMessage. Calling from outside 59 // of State is not allowed. 60 private boolean mSelfMsgAllowed = false; 61 62 /** 63 * A information class about a state and its parent. Used to maintain the state hierarchy. 64 */ 65 public static class StateInfo { 66 /** The state who owns this StateInfo. */ 67 public final State state; 68 /** The parent state. */ 69 public final State parent; 70 // True when the state has been entered and on the stack. 71 private boolean mActive; 72 StateInfo(@onNull final State child, @Nullable final State parent)73 public StateInfo(@NonNull final State child, @Nullable final State parent) { 74 this.state = child; 75 this.parent = parent; 76 } 77 } 78 79 /** 80 * The constructor. 81 * 82 * @param name of this machine. 83 * @param thread the running thread of this machine. It must either be the thread on which this 84 * constructor is called, or a thread that is not started yet. 85 */ SyncStateMachine(@onNull final String name, @NonNull final Thread thread)86 public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread) { 87 this(name, thread, false /* debug */); 88 } 89 90 /** 91 * The constructor. 92 * 93 * @param name of this machine. 94 * @param thread the running thread of this machine. It must either be the thread on which this 95 * constructor is called, or a thread that is not started yet. 96 * @param dbg whether to print debug logs. 97 */ SyncStateMachine(@onNull final String name, @NonNull final Thread thread, final boolean dbg)98 public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread, 99 final boolean dbg) { 100 mMyThread = thread; 101 // Machine can either be setup from machine thread or before machine thread started. 102 ensureCorrectOrNotStartedThread(); 103 104 mName = name; 105 mDbg = dbg; 106 } 107 108 /** 109 * Add all of states to the state machine. Different StateInfos which have same state are not 110 * allowed. In other words, a state can not have multiple parent states. #addAllStates can 111 * only be called once either from mMyThread or before mMyThread started. 112 */ addAllStates(@onNull final List<StateInfo> stateInfos)113 public final void addAllStates(@NonNull final List<StateInfo> stateInfos) { 114 ensureCorrectOrNotStartedThread(); 115 116 if (mCurrentState != null) { 117 throw new IllegalStateException("State only can be added before started"); 118 } 119 120 if (stateInfos.isEmpty()) throw new IllegalStateException("Empty state is not allowed"); 121 122 if (!mStateInfo.isEmpty()) throw new IllegalStateException("States are already configured"); 123 124 final Set<Class> usedClasses = new ArraySet<>(); 125 for (final StateInfo info : stateInfos) { 126 Objects.requireNonNull(info.state); 127 if (!usedClasses.add(info.state.getClass())) { 128 throw new IllegalStateException("Adding the same state multiple times in a state " 129 + "machine is forbidden because it tends to be confusing; it can be done " 130 + "with anonymous subclasses but consider carefully whether you want to " 131 + "use a single state or other alternatives instead."); 132 } 133 134 mStateInfo.put(info.state, info); 135 } 136 137 // Check whether all of parent states indicated from StateInfo are added. 138 for (final StateInfo info : stateInfos) { 139 if (info.parent != null) ensureExistingState(info.parent); 140 } 141 } 142 143 /** 144 * Start the state machine. The initial state can't be child state. 145 * 146 * @param initialState the first state of this machine. The state must be exact state object 147 * setting up by {@link #addAllStates}, not a copy of it. 148 */ start(@onNull final State initialState)149 public final void start(@NonNull final State initialState) { 150 ensureCorrectThread(); 151 ensureExistingState(initialState); 152 153 mDestState = initialState; 154 mSelfMsgAllowed = true; 155 performTransitions(); 156 mSelfMsgAllowed = false; 157 // If sendSelfMessage was called inside initialState#enter(), mSelfMsgQueue must be 158 // processed. 159 maybeProcessSelfMessageQueue(); 160 } 161 162 /** 163 * Process the message synchronously then perform state transition. This method is used 164 * externally to the automaton to request that the automaton process the given message. 165 * The message is processed sequentially, so calling this method recursively is not permitted. 166 * In other words, using this method inside State#enter, State#exit, or State#processMessage 167 * is incorrect and will result in an IllegalStateException. 168 * 169 * @param what is assigned to Message.what 170 * @param arg1 is assigned to Message.arg1 171 * @param arg2 is assigned to Message.arg2 172 * @param obj is assigned to Message.obj 173 */ processMessage(int what, int arg1, int arg2, @Nullable Object obj)174 public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) { 175 ensureCorrectThread(); 176 177 if (mCurrentlyProcessing != Integer.MIN_VALUE) { 178 throw new IllegalStateException("Message(" + mCurrentlyProcessing 179 + ") is still being processed"); 180 } 181 182 // mCurrentlyProcessing tracks the external message request and it prevents this method to 183 // be called recursively. Once this message is processed and the transitions have been 184 // performed, the automaton will process the self message queue. The messages in the self 185 // message queue are added from within the automaton during processing external message. 186 // mCurrentlyProcessing is still the original external one and it will not prevent self 187 // messages from being processed. 188 mCurrentlyProcessing = what; 189 final Message msg = Message.obtain(null, what, arg1, arg2, obj); 190 currentStateProcessMessageThenPerformTransitions(msg); 191 msg.recycle(); 192 maybeProcessSelfMessageQueue(); 193 194 mCurrentlyProcessing = Integer.MIN_VALUE; 195 } 196 197 /** 198 * Synchronously process a message and perform state transition. 199 * 200 * @param what is assigned to Message.what. 201 */ processMessage(int what)202 public final void processMessage(int what) { 203 processMessage(what, 0, 0, null); 204 } 205 maybeProcessSelfMessageQueue()206 private void maybeProcessSelfMessageQueue() { 207 while (!mSelfMsgQueue.isEmpty()) { 208 currentStateProcessMessageThenPerformTransitions(mSelfMsgQueue.poll()); 209 } 210 } 211 currentStateProcessMessageThenPerformTransitions(@onNull final Message msg)212 private void currentStateProcessMessageThenPerformTransitions(@NonNull final Message msg) { 213 mSelfMsgAllowed = true; 214 StateInfo consideredState = mStateInfo.get(mCurrentState); 215 while (null != consideredState) { 216 // Ideally this should compare with IState.HANDLED, but it is not public field so just 217 // checking whether the return value is true (IState.HANDLED = true). 218 if (consideredState.state.processMessage(msg)) { 219 if (mDbg) { 220 Log.d(mName, "State " + consideredState.state 221 + " processed message " + msg.what); 222 } 223 break; 224 } 225 consideredState = mStateInfo.get(consideredState.parent); 226 } 227 if (null == consideredState) { 228 final String state = mCurrentState == null ? "null" : mCurrentState.getName(); 229 Log.wtf(mName, "Message " + msg.what + " was not handled. Current state: " + state); 230 } 231 232 performTransitions(); 233 mSelfMsgAllowed = false; 234 } 235 236 /** 237 * Send self message during state transition. 238 * 239 * Must only be used inside State processMessage, enter or exit. The typical use case is 240 * something wrong happens during state transition, sending an error message which would be 241 * handled after finishing current state transitions. 242 */ sendSelfMessage(int what, int arg1, int arg2, Object obj)243 public final void sendSelfMessage(int what, int arg1, int arg2, Object obj) { 244 if (!mSelfMsgAllowed) { 245 throw new IllegalStateException("sendSelfMessage can only be called inside " 246 + "State#enter, State#exit or State#processMessage"); 247 } 248 249 mSelfMsgQueue.add(Message.obtain(null, what, arg1, arg2, obj)); 250 } 251 252 /** 253 * Transition to destination state. Upon returning from processMessage the automaton will 254 * transition to the given destination state. 255 * 256 * This function can NOT be called inside the State enter and exit function. The transition 257 * target is always defined and can never be changed mid-way of state transition. 258 * 259 * @param destState will be the state to transition to. The state must be the same instance set 260 * up by {@link #addAllStates}, not a copy of it. 261 */ transitionTo(@onNull final State destState)262 public final void transitionTo(@NonNull final State destState) { 263 if (mDbg) Log.d(mName, "transitionTo " + destState); 264 ensureCorrectThread(); 265 ensureExistingState(destState); 266 267 if (mDestState == mCurrentState) { 268 mDestState = destState; 269 } else { 270 throw new IllegalStateException("Destination already specified"); 271 } 272 } 273 performTransitions()274 private void performTransitions() { 275 // 1. Determine the common ancestor state of current/destination states 276 // 2. Invoke state exit list from current state to common ancestor state. 277 // 3. Invoke state enter list from common ancestor state to destState by going 278 // through mEnterStateStack. 279 if (mDestState == mCurrentState) return; 280 281 final StateInfo commonAncestor = getLastActiveAncestor(mStateInfo.get(mDestState)); 282 283 executeExitMethods(commonAncestor, mStateInfo.get(mCurrentState)); 284 executeEnterMethods(commonAncestor, mStateInfo.get(mDestState)); 285 mCurrentState = mDestState; 286 } 287 288 // Null is the root of all states. getLastActiveAncestor(@ullable final StateInfo start)289 private StateInfo getLastActiveAncestor(@Nullable final StateInfo start) { 290 if (null == start || start.mActive) return start; 291 292 return getLastActiveAncestor(mStateInfo.get(start.parent)); 293 } 294 295 // Call the exit method from current state to common ancestor state. 296 // Both the commonAncestor and exitingState StateInfo can be null because null is the ancestor 297 // of all states. 298 // For example: When transitioning from state1 to state2, the 299 // executeExitMethods(commonAncestor, exitingState) function will be called twice, once with 300 // null and state1 as the argument, and once with null and null as the argument. 301 // root 302 // | \ 303 // current <- state1 state2 -> destination executeExitMethods(@ullable StateInfo commonAncestor, @Nullable StateInfo exitingState)304 private void executeExitMethods(@Nullable StateInfo commonAncestor, 305 @Nullable StateInfo exitingState) { 306 if (commonAncestor == exitingState) return; 307 308 if (mDbg) Log.d(mName, exitingState.state + " exit()"); 309 exitingState.state.exit(); 310 exitingState.mActive = false; 311 executeExitMethods(commonAncestor, mStateInfo.get(exitingState.parent)); 312 } 313 314 // Call the enter method from common ancestor state to destination state. 315 // Both the commonAncestor and enteringState StateInfo can be null because null is the ancestor 316 // of all states. 317 // For example: When transitioning from state1 to state2, the 318 // executeEnterMethods(commonAncestor, enteringState) function will be called twice, once with 319 // null and state2 as the argument, and once with null and null as the argument. 320 // root 321 // | \ 322 // current <- state1 state2 -> destination executeEnterMethods(@ullable StateInfo commonAncestor, @Nullable StateInfo enteringState)323 private void executeEnterMethods(@Nullable StateInfo commonAncestor, 324 @Nullable StateInfo enteringState) { 325 if (enteringState == commonAncestor) return; 326 327 executeEnterMethods(commonAncestor, mStateInfo.get(enteringState.parent)); 328 if (mDbg) Log.d(mName, enteringState.state + " enter()"); 329 enteringState.state.enter(); 330 enteringState.mActive = true; 331 } 332 ensureCorrectThread()333 private void ensureCorrectThread() { 334 if (!mMyThread.equals(Thread.currentThread())) { 335 throw new IllegalStateException("Called from wrong thread"); 336 } 337 } 338 ensureCorrectOrNotStartedThread()339 private void ensureCorrectOrNotStartedThread() { 340 if (!mMyThread.isAlive()) return; 341 342 ensureCorrectThread(); 343 } 344 ensureExistingState(@onNull final State state)345 private void ensureExistingState(@NonNull final State state) { 346 if (!mStateInfo.containsKey(state)) throw new IllegalStateException("Invalid state"); 347 } 348 } 349