StateMachine.java revision f643069ad56763732a06ab601cea1272e4c1827a
1/** 2 * Copyright (C) 2009 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 17package com.android.internal.util; 18 19import android.os.Handler; 20import android.os.HandlerThread; 21import android.os.Looper; 22import android.os.Message; 23import android.text.TextUtils; 24import android.util.Log; 25 26import java.io.FileDescriptor; 27import java.io.PrintWriter; 28import java.util.ArrayList; 29import java.util.Calendar; 30import java.util.Collection; 31import java.util.HashMap; 32import java.util.Vector; 33 34/** 35 * {@hide} 36 * 37 * <p>The state machine defined here is a hierarchical state machine which processes messages 38 * and can have states arranged hierarchically.</p> 39 * 40 * <p>A state is a <code>State</code> object and must implement 41 * <code>processMessage</code> and optionally <code>enter/exit/getName</code>. 42 * The enter/exit methods are equivalent to the construction and destruction 43 * in Object Oriented programming and are used to perform initialization and 44 * cleanup of the state respectively. The <code>getName</code> method returns the 45 * name of the state the default implementation returns the class name it may be 46 * desirable to have this return the name of the state instance name instead. 47 * In particular if a particular state class has multiple instances.</p> 48 * 49 * <p>When a state machine is created <code>addState</code> is used to build the 50 * hierarchy and <code>setInitialState</code> is used to identify which of these 51 * is the initial state. After construction the programmer calls <code>start</code> 52 * which initializes and starts the state machine. The first action the StateMachine 53 * is to the invoke <code>enter</code> for all of the initial state's hierarchy, 54 * starting at its eldest parent. The calls to enter will be done in the context 55 * of the StateMachines Handler not in the context of the call to start and they 56 * will be invoked before any messages are processed. For example, given the simple 57 * state machine below mP1.enter will be invoked and then mS1.enter. Finally, 58 * messages sent to the state machine will be processed by the current state, 59 * in our simple state machine below that would initially be mS1.processMessage.</p> 60<code> 61 mP1 62 / \ 63 mS2 mS1 ----> initial state 64</code> 65 * <p>After the state machine is created and started, messages are sent to a state 66 * machine using <code>sendMessage</code> and the messages are created using 67 * <code>obtainMessage</code>. When the state machine receives a message the 68 * current state's <code>processMessage</code> is invoked. In the above example 69 * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code> 70 * to change the current state to a new state</p> 71 * 72 * <p>Each state in the state machine may have a zero or one parent states and if 73 * a child state is unable to handle a message it may have the message processed 74 * by its parent by returning false or NOT_HANDLED. If a message is never processed 75 * <code>unhandledMessage</code> will be invoked to give one last chance for the state machine 76 * to process the message.</p> 77 * 78 * <p>When all processing is completed a state machine may choose to call 79 * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code> 80 * returns the state machine will transfer to an internal <code>HaltingState</code> 81 * and invoke <code>halting</code>. Any message subsequently received by the state 82 * machine will cause <code>haltedProcessMessage</code> to be invoked.</p> 83 * 84 * <p>If it is desirable to completely stop the state machine call <code>quit</code> or 85 * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents, 86 * call <code>onQuiting</code> and then exit Thread/Loopers.</p> 87 * 88 * <p>In addition to <code>processMessage</code> each <code>State</code> has 89 * an <code>enter</code> method and <code>exit</exit> method which may be overridden.</p> 90 * 91 * <p>Since the states are arranged in a hierarchy transitioning to a new state 92 * causes current states to be exited and new states to be entered. To determine 93 * the list of states to be entered/exited the common parent closest to 94 * the current state is found. We then exit from the current state and its 95 * parent's up to but not including the common parent state and then enter all 96 * of the new states below the common parent down to the destination state. 97 * If there is no common parent all states are exited and then the new states 98 * are entered.</p> 99 * 100 * <p>Two other methods that states can use are <code>deferMessage</code> and 101 * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends 102 * a message but places it on the front of the queue rather than the back. The 103 * <code>deferMessage</code> causes the message to be saved on a list until a 104 * transition is made to a new state. At which time all of the deferred messages 105 * will be put on the front of the state machine queue with the oldest message 106 * at the front. These will then be processed by the new current state before 107 * any other messages that are on the queue or might be added later. Both of 108 * these are protected and may only be invoked from within a state machine.</p> 109 * 110 * <p>To illustrate some of these properties we'll use state machine with an 8 111 * state hierarchy:</p> 112<code> 113 mP0 114 / \ 115 mP1 mS0 116 / \ 117 mS2 mS1 118 / \ \ 119 mS3 mS4 mS5 ---> initial state 120</code> 121 * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. 122 * So the order of calling processMessage when a message is received is mS5, 123 * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this 124 * message by returning false or NOT_HANDLED.</p> 125 * 126 * <p>Now assume mS5.processMessage receives a message it can handle, and during 127 * the handling determines the machine should change states. It could call 128 * transitionTo(mS4) and return true or HANDLED. Immediately after returning from 129 * processMessage the state machine runtime will find the common parent, 130 * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then 131 * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So 132 * when the next message is received mS4.processMessage will be invoked.</p> 133 * 134 * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine. 135 * It responds with "Hello World" being printed to the log for every message.</p> 136<code> 137class HelloWorld extends StateMachine { 138 HelloWorld(String name) { 139 super(name); 140 addState(mState1); 141 setInitialState(mState1); 142 } 143 144 public static HelloWorld makeHelloWorld() { 145 HelloWorld hw = new HelloWorld("hw"); 146 hw.start(); 147 return hw; 148 } 149 150 class State1 extends State { 151 @Override public boolean processMessage(Message message) { 152 log("Hello World"); 153 return HANDLED; 154 } 155 } 156 State1 mState1 = new State1(); 157} 158 159void testHelloWorld() { 160 HelloWorld hw = makeHelloWorld(); 161 hw.sendMessage(hw.obtainMessage()); 162} 163</code> 164 * <p>A more interesting state machine is one with four states 165 * with two independent parent states.</p> 166<code> 167 mP1 mP2 168 / \ 169 mS2 mS1 170</code> 171 * <p>Here is a description of this state machine using pseudo code.</p> 172 <code> 173state mP1 { 174 enter { log("mP1.enter"); } 175 exit { log("mP1.exit"); } 176 on msg { 177 CMD_2 { 178 send(CMD_3); 179 defer(msg); 180 transitonTo(mS2); 181 return HANDLED; 182 } 183 return NOT_HANDLED; 184 } 185} 186 187INITIAL 188state mS1 parent mP1 { 189 enter { log("mS1.enter"); } 190 exit { log("mS1.exit"); } 191 on msg { 192 CMD_1 { 193 transitionTo(mS1); 194 return HANDLED; 195 } 196 return NOT_HANDLED; 197 } 198} 199 200state mS2 parent mP1 { 201 enter { log("mS2.enter"); } 202 exit { log("mS2.exit"); } 203 on msg { 204 CMD_2 { 205 send(CMD_4); 206 return HANDLED; 207 } 208 CMD_3 { 209 defer(msg); 210 transitionTo(mP2); 211 return HANDLED; 212 } 213 return NOT_HANDLED; 214 } 215} 216 217state mP2 { 218 enter { 219 log("mP2.enter"); 220 send(CMD_5); 221 } 222 exit { log("mP2.exit"); } 223 on msg { 224 CMD_3, CMD_4 { return HANDLED; } 225 CMD_5 { 226 transitionTo(HaltingState); 227 return HANDLED; 228 } 229 return NOT_HANDLED; 230 } 231} 232</code> 233 * <p>The implementation is below and also in StateMachineTest:</p> 234<code> 235class Hsm1 extends StateMachine { 236 public static final int CMD_1 = 1; 237 public static final int CMD_2 = 2; 238 public static final int CMD_3 = 3; 239 public static final int CMD_4 = 4; 240 public static final int CMD_5 = 5; 241 242 public static Hsm1 makeHsm1() { 243 log("makeHsm1 E"); 244 Hsm1 sm = new Hsm1("hsm1"); 245 sm.start(); 246 log("makeHsm1 X"); 247 return sm; 248 } 249 250 Hsm1(String name) { 251 super(name); 252 log("ctor E"); 253 254 // Add states, use indentation to show hierarchy 255 addState(mP1); 256 addState(mS1, mP1); 257 addState(mS2, mP1); 258 addState(mP2); 259 260 // Set the initial state 261 setInitialState(mS1); 262 log("ctor X"); 263 } 264 265 class P1 extends State { 266 @Override public void enter() { 267 log("mP1.enter"); 268 } 269 @Override public boolean processMessage(Message message) { 270 boolean retVal; 271 log("mP1.processMessage what=" + message.what); 272 switch(message.what) { 273 case CMD_2: 274 // CMD_2 will arrive in mS2 before CMD_3 275 sendMessage(obtainMessage(CMD_3)); 276 deferMessage(message); 277 transitionTo(mS2); 278 retVal = HANDLED; 279 break; 280 default: 281 // Any message we don't understand in this state invokes unhandledMessage 282 retVal = NOT_HANDLED; 283 break; 284 } 285 return retVal; 286 } 287 @Override public void exit() { 288 log("mP1.exit"); 289 } 290 } 291 292 class S1 extends State { 293 @Override public void enter() { 294 log("mS1.enter"); 295 } 296 @Override public boolean processMessage(Message message) { 297 log("S1.processMessage what=" + message.what); 298 if (message.what == CMD_1) { 299 // Transition to ourself to show that enter/exit is called 300 transitionTo(mS1); 301 return HANDLED; 302 } else { 303 // Let parent process all other messages 304 return NOT_HANDLED; 305 } 306 } 307 @Override public void exit() { 308 log("mS1.exit"); 309 } 310 } 311 312 class S2 extends State { 313 @Override public void enter() { 314 log("mS2.enter"); 315 } 316 @Override public boolean processMessage(Message message) { 317 boolean retVal; 318 log("mS2.processMessage what=" + message.what); 319 switch(message.what) { 320 case(CMD_2): 321 sendMessage(obtainMessage(CMD_4)); 322 retVal = HANDLED; 323 break; 324 case(CMD_3): 325 deferMessage(message); 326 transitionTo(mP2); 327 retVal = HANDLED; 328 break; 329 default: 330 retVal = NOT_HANDLED; 331 break; 332 } 333 return retVal; 334 } 335 @Override public void exit() { 336 log("mS2.exit"); 337 } 338 } 339 340 class P2 extends State { 341 @Override public void enter() { 342 log("mP2.enter"); 343 sendMessage(obtainMessage(CMD_5)); 344 } 345 @Override public boolean processMessage(Message message) { 346 log("P2.processMessage what=" + message.what); 347 switch(message.what) { 348 case(CMD_3): 349 break; 350 case(CMD_4): 351 break; 352 case(CMD_5): 353 transitionToHaltingState(); 354 break; 355 } 356 return HANDLED; 357 } 358 @Override public void exit() { 359 log("mP2.exit"); 360 } 361 } 362 363 @Override 364 void onHalting() { 365 log("halting"); 366 synchronized (this) { 367 this.notifyAll(); 368 } 369 } 370 371 P1 mP1 = new P1(); 372 S1 mS1 = new S1(); 373 S2 mS2 = new S2(); 374 P2 mP2 = new P2(); 375} 376</code> 377 * <p>If this is executed by sending two messages CMD_1 and CMD_2 378 * (Note the synchronize is only needed because we use hsm.wait())</p> 379<code> 380Hsm1 hsm = makeHsm1(); 381synchronize(hsm) { 382 hsm.sendMessage(obtainMessage(hsm.CMD_1)); 383 hsm.sendMessage(obtainMessage(hsm.CMD_2)); 384 try { 385 // wait for the messages to be handled 386 hsm.wait(); 387 } catch (InterruptedException e) { 388 loge("exception while waiting " + e.getMessage()); 389 } 390} 391</code> 392 * <p>The output is:</p> 393<code> 394D/hsm1 ( 1999): makeHsm1 E 395D/hsm1 ( 1999): ctor E 396D/hsm1 ( 1999): ctor X 397D/hsm1 ( 1999): mP1.enter 398D/hsm1 ( 1999): mS1.enter 399D/hsm1 ( 1999): makeHsm1 X 400D/hsm1 ( 1999): mS1.processMessage what=1 401D/hsm1 ( 1999): mS1.exit 402D/hsm1 ( 1999): mS1.enter 403D/hsm1 ( 1999): mS1.processMessage what=2 404D/hsm1 ( 1999): mP1.processMessage what=2 405D/hsm1 ( 1999): mS1.exit 406D/hsm1 ( 1999): mS2.enter 407D/hsm1 ( 1999): mS2.processMessage what=2 408D/hsm1 ( 1999): mS2.processMessage what=3 409D/hsm1 ( 1999): mS2.exit 410D/hsm1 ( 1999): mP1.exit 411D/hsm1 ( 1999): mP2.enter 412D/hsm1 ( 1999): mP2.processMessage what=3 413D/hsm1 ( 1999): mP2.processMessage what=4 414D/hsm1 ( 1999): mP2.processMessage what=5 415D/hsm1 ( 1999): mP2.exit 416D/hsm1 ( 1999): halting 417</code> 418 */ 419public class StateMachine { 420 // Name of the state machine and used as logging tag 421 private String mName; 422 423 /** Message.what value when quitting */ 424 private static final int SM_QUIT_CMD = -1; 425 426 /** Message.what value when initializing */ 427 private static final int SM_INIT_CMD = -2; 428 429 /** 430 * Convenience constant that maybe returned by processMessage 431 * to indicate the the message was processed and is not to be 432 * processed by parent states 433 */ 434 public static final boolean HANDLED = true; 435 436 /** 437 * Convenience constant that maybe returned by processMessage 438 * to indicate the the message was NOT processed and is to be 439 * processed by parent states 440 */ 441 public static final boolean NOT_HANDLED = false; 442 443 /** 444 * StateMachine logging record. 445 * {@hide} 446 */ 447 public static class LogRec { 448 private StateMachine mSm; 449 private long mTime; 450 private int mWhat; 451 private String mInfo; 452 private IState mState; 453 private IState mOrgState; 454 private IState mDstState; 455 456 /** 457 * Constructor 458 * 459 * @param msg 460 * @param state the state which handled the message 461 * @param orgState is the first state the received the message but 462 * did not processes the message. 463 * @param transToState is the state that was transitioned to after the message was 464 * processed. 465 */ 466 LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState, 467 IState transToState) { 468 update(sm, msg, info, state, orgState, transToState); 469 } 470 471 /** 472 * Update the information in the record. 473 * @param state that handled the message 474 * @param orgState is the first state the received the message 475 * @param dstState is the state that was the transition target when logging 476 */ 477 public void update(StateMachine sm, Message msg, String info, IState state, IState orgState, 478 IState dstState) { 479 mSm = sm; 480 mTime = System.currentTimeMillis(); 481 mWhat = (msg != null) ? msg.what : 0; 482 mInfo = info; 483 mState = state; 484 mOrgState = orgState; 485 mDstState = dstState; 486 } 487 488 /** 489 * @return time stamp 490 */ 491 public long getTime() { 492 return mTime; 493 } 494 495 /** 496 * @return msg.what 497 */ 498 public long getWhat() { 499 return mWhat; 500 } 501 502 /** 503 * @return the command that was executing 504 */ 505 public String getInfo() { 506 return mInfo; 507 } 508 509 /** 510 * @return the state that handled this message 511 */ 512 public IState getState() { 513 return mState; 514 } 515 516 /** 517 * @return the state destination state if a transition is occurring or null if none. 518 */ 519 public IState getDestState() { 520 return mDstState; 521 } 522 523 524 /** 525 * @return the original state that received the message. 526 */ 527 public IState getOriginalState() { 528 return mOrgState; 529 } 530 531 @Override 532 public String toString() { 533 StringBuilder sb = new StringBuilder(); 534 sb.append("time="); 535 Calendar c = Calendar.getInstance(); 536 c.setTimeInMillis(mTime); 537 sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 538 sb.append(" processed="); 539 sb.append(mState == null ? "<null>" : mState.getName()); 540 sb.append(" org="); 541 sb.append(mOrgState == null ? "<null>" : mOrgState.getName()); 542 sb.append(" dest="); 543 sb.append(mDstState == null ? "<null>" : mDstState.getName()); 544 sb.append(" what="); 545 String what = mSm != null ? mSm.getWhatToString(mWhat) : ""; 546 if (TextUtils.isEmpty(what)) { 547 sb.append(mWhat); 548 sb.append("(0x"); 549 sb.append(Integer.toHexString(mWhat)); 550 sb.append(")"); 551 } else { 552 sb.append(what); 553 } 554 if ( ! TextUtils.isEmpty(mInfo)) { 555 sb.append(" "); 556 sb.append(mInfo); 557 } 558 return sb.toString(); 559 } 560 } 561 562 /** 563 * A list of log records including messages recently processed by the state machine. 564 * 565 * The class maintains a list of log records including messages 566 * recently processed. The list is finite and may be set in the 567 * constructor or by calling setSize. The public interface also 568 * includes size which returns the number of recent records, 569 * count which is the number of records processed since the 570 * the last setSize, get which returns a record and 571 * add which adds a record. 572 */ 573 private static class LogRecords { 574 575 private static final int DEFAULT_SIZE = 20; 576 577 private Vector<LogRec> mLogRecVector = new Vector<LogRec>(); 578 private int mMaxSize = DEFAULT_SIZE; 579 private int mOldestIndex = 0; 580 private int mCount = 0; 581 private boolean mLogOnlyTransitions = false; 582 583 /** 584 * private constructor use add 585 */ 586 private LogRecords() { 587 } 588 589 /** 590 * Set size of messages to maintain and clears all current records. 591 * 592 * @param maxSize number of records to maintain at anyone time. 593 */ 594 synchronized void setSize(int maxSize) { 595 mMaxSize = maxSize; 596 mCount = 0; 597 mLogRecVector.clear(); 598 } 599 600 synchronized void setLogOnlyTransitions(boolean enable) { 601 mLogOnlyTransitions = enable; 602 } 603 604 synchronized boolean logOnlyTransitions() { 605 return mLogOnlyTransitions; 606 } 607 608 /** 609 * @return the number of recent records. 610 */ 611 synchronized int size() { 612 return mLogRecVector.size(); 613 } 614 615 /** 616 * @return the total number of records processed since size was set. 617 */ 618 synchronized int count() { 619 return mCount; 620 } 621 622 /** 623 * Clear the list of records. 624 */ 625 synchronized void cleanup() { 626 mLogRecVector.clear(); 627 } 628 629 /** 630 * @return the information on a particular record. 0 is the oldest 631 * record and size()-1 is the newest record. If the index is to 632 * large null is returned. 633 */ 634 synchronized LogRec get(int index) { 635 int nextIndex = mOldestIndex + index; 636 if (nextIndex >= mMaxSize) { 637 nextIndex -= mMaxSize; 638 } 639 if (nextIndex >= size()) { 640 return null; 641 } else { 642 return mLogRecVector.get(nextIndex); 643 } 644 } 645 646 /** 647 * Add a processed message. 648 * 649 * @param msg 650 * @param messageInfo to be stored 651 * @param state that handled the message 652 * @param orgState is the first state the received the message but 653 * did not processes the message. 654 * @param transToState is the state that was transitioned to after the message was 655 * processed. 656 * 657 */ 658 synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state, 659 IState orgState, 660 IState transToState) { 661 mCount += 1; 662 if (mLogRecVector.size() < mMaxSize) { 663 mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState)); 664 } else { 665 LogRec pmi = mLogRecVector.get(mOldestIndex); 666 mOldestIndex += 1; 667 if (mOldestIndex >= mMaxSize) { 668 mOldestIndex = 0; 669 } 670 pmi.update(sm, msg, messageInfo, state, orgState, transToState); 671 } 672 } 673 } 674 675 676 private static class SmHandler extends Handler { 677 678 /** The debug flag */ 679 private boolean mDbg = false; 680 681 /** The SmHandler object, identifies that message is internal */ 682 private static final Object mSmHandlerObj = new Object(); 683 684 /** The current message */ 685 private Message mMsg; 686 687 /** A list of log records including messages this state machine has processed */ 688 private LogRecords mLogRecords = new LogRecords(); 689 690 /** true if construction of the state machine has not been completed */ 691 private boolean mIsConstructionCompleted; 692 693 /** Stack used to manage the current hierarchy of states */ 694 private StateInfo mStateStack[]; 695 696 /** Top of mStateStack */ 697 private int mStateStackTopIndex = -1; 698 699 /** A temporary stack used to manage the state stack */ 700 private StateInfo mTempStateStack[]; 701 702 /** The top of the mTempStateStack */ 703 private int mTempStateStackCount; 704 705 /** State used when state machine is halted */ 706 private HaltingState mHaltingState = new HaltingState(); 707 708 /** State used when state machine is quitting */ 709 private QuittingState mQuittingState = new QuittingState(); 710 711 /** Reference to the StateMachine */ 712 private StateMachine mSm; 713 714 /** 715 * Information about a state. 716 * Used to maintain the hierarchy. 717 */ 718 private class StateInfo { 719 /** The state */ 720 State state; 721 722 /** The parent of this state, null if there is no parent */ 723 StateInfo parentStateInfo; 724 725 /** True when the state has been entered and on the stack */ 726 boolean active; 727 728 /** 729 * Convert StateInfo to string 730 */ 731 @Override 732 public String toString() { 733 return "state=" + state.getName() + ",active=" + active 734 + ",parent=" + ((parentStateInfo == null) ? 735 "null" : parentStateInfo.state.getName()); 736 } 737 } 738 739 /** The map of all of the states in the state machine */ 740 private HashMap<State, StateInfo> mStateInfo = 741 new HashMap<State, StateInfo>(); 742 743 /** The initial state that will process the first message */ 744 private State mInitialState; 745 746 /** The destination state when transitionTo has been invoked */ 747 private State mDestState; 748 749 /** The list of deferred messages */ 750 private ArrayList<Message> mDeferredMessages = new ArrayList<Message>(); 751 752 /** 753 * State entered when transitionToHaltingState is called. 754 */ 755 private class HaltingState extends State { 756 @Override 757 public boolean processMessage(Message msg) { 758 mSm.haltedProcessMessage(msg); 759 return true; 760 } 761 } 762 763 /** 764 * State entered when a valid quit message is handled. 765 */ 766 private class QuittingState extends State { 767 @Override 768 public boolean processMessage(Message msg) { 769 return NOT_HANDLED; 770 } 771 } 772 773 /** 774 * Handle messages sent to the state machine by calling 775 * the current state's processMessage. It also handles 776 * the enter/exit calls and placing any deferred messages 777 * back onto the queue when transitioning to a new state. 778 */ 779 @Override 780 public final void handleMessage(Message msg) { 781 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what); 782 783 /** Save the current message */ 784 mMsg = msg; 785 786 /** State that processed the message */ 787 State msgProcessedState = null; 788 if (mIsConstructionCompleted) { 789 /** Normal path */ 790 msgProcessedState = processMsg(msg); 791 } else if (!mIsConstructionCompleted && 792 (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) { 793 /** Initial one time path. */ 794 mIsConstructionCompleted = true; 795 invokeEnterMethods(0); 796 } else { 797 throw new RuntimeException("StateMachine.handleMessage: " + 798 "The start method not called, received msg: " + msg); 799 } 800 performTransitions(msgProcessedState, msg); 801 802 // We need to check if mSm == null here as we could be quitting. 803 if (mDbg && mSm != null) mSm.log("handleMessage: X"); 804 } 805 806 /** 807 * Do any transitions 808 * @param msgProcessedState is the state that processed the message 809 */ 810 private void performTransitions(State msgProcessedState, Message msg) { 811 /** 812 * If transitionTo has been called, exit and then enter 813 * the appropriate states. We loop on this to allow 814 * enter and exit methods to use transitionTo. 815 */ 816 State orgState = mStateStack[mStateStackTopIndex].state; 817 818 /** 819 * Record whether message needs to be logged before we transition and 820 * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which 821 * always set msg.obj to the handler. 822 */ 823 boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj); 824 825 if (mLogRecords.logOnlyTransitions()) { 826 /** Record only if there is a transition */ 827 if (mDestState != null) { 828 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, 829 orgState, mDestState); 830 } 831 } else if (recordLogMsg) { 832 /** Record message */ 833 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, 834 orgState, mDestState); 835 } 836 837 State destState = mDestState; 838 if (destState != null) { 839 /** 840 * Process the transitions including transitions in the enter/exit methods 841 */ 842 while (true) { 843 if (mDbg) mSm.log("handleMessage: new destination call exit/enter"); 844 845 /** 846 * Determine the states to exit and enter and return the 847 * common ancestor state of the enter/exit states. Then 848 * invoke the exit methods then the enter methods. 849 */ 850 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); 851 invokeExitMethods(commonStateInfo); 852 int stateStackEnteringIndex = moveTempStateStackToStateStack(); 853 invokeEnterMethods(stateStackEnteringIndex); 854 855 856 /** 857 * Since we have transitioned to a new state we need to have 858 * any deferred messages moved to the front of the message queue 859 * so they will be processed before any other messages in the 860 * message queue. 861 */ 862 moveDeferredMessageAtFrontOfQueue(); 863 864 if (destState != mDestState) { 865 // A new mDestState so continue looping 866 destState = mDestState; 867 } else { 868 // No change in mDestState so we're done 869 break; 870 } 871 } 872 mDestState = null; 873 } 874 875 /** 876 * After processing all transitions check and 877 * see if the last transition was to quit or halt. 878 */ 879 if (destState != null) { 880 if (destState == mQuittingState) { 881 /** 882 * Call onQuitting to let subclasses cleanup. 883 */ 884 mSm.onQuitting(); 885 cleanupAfterQuitting(); 886 } else if (destState == mHaltingState) { 887 /** 888 * Call onHalting() if we've transitioned to the halting 889 * state. All subsequent messages will be processed in 890 * in the halting state which invokes haltedProcessMessage(msg); 891 */ 892 mSm.onHalting(); 893 } 894 } 895 } 896 897 /** 898 * Cleanup all the static variables and the looper after the SM has been quit. 899 */ 900 private final void cleanupAfterQuitting() { 901 if (mSm.mSmThread != null) { 902 // If we made the thread then quit looper which stops the thread. 903 getLooper().quit(); 904 mSm.mSmThread = null; 905 } 906 907 mSm.mSmHandler = null; 908 mSm = null; 909 mMsg = null; 910 mLogRecords.cleanup(); 911 mStateStack = null; 912 mTempStateStack = null; 913 mStateInfo.clear(); 914 mInitialState = null; 915 mDestState = null; 916 mDeferredMessages.clear(); 917 } 918 919 /** 920 * Complete the construction of the state machine. 921 */ 922 private final void completeConstruction() { 923 if (mDbg) mSm.log("completeConstruction: E"); 924 925 /** 926 * Determine the maximum depth of the state hierarchy 927 * so we can allocate the state stacks. 928 */ 929 int maxDepth = 0; 930 for (StateInfo si : mStateInfo.values()) { 931 int depth = 0; 932 for (StateInfo i = si; i != null; depth++) { 933 i = i.parentStateInfo; 934 } 935 if (maxDepth < depth) { 936 maxDepth = depth; 937 } 938 } 939 if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth); 940 941 mStateStack = new StateInfo[maxDepth]; 942 mTempStateStack = new StateInfo[maxDepth]; 943 setupInitialStateStack(); 944 945 /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */ 946 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); 947 948 if (mDbg) mSm.log("completeConstruction: X"); 949 } 950 951 /** 952 * Process the message. If the current state doesn't handle 953 * it, call the states parent and so on. If it is never handled then 954 * call the state machines unhandledMessage method. 955 * @return the state that processed the message 956 */ 957 private final State processMsg(Message msg) { 958 StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; 959 if (mDbg) { 960 mSm.log("processMsg: " + curStateInfo.state.getName()); 961 } 962 963 if (isQuit(msg)) { 964 transitionTo(mQuittingState); 965 } else { 966 while (!curStateInfo.state.processMessage(msg)) { 967 /** 968 * Not processed 969 */ 970 curStateInfo = curStateInfo.parentStateInfo; 971 if (curStateInfo == null) { 972 /** 973 * No parents left so it's not handled 974 */ 975 mSm.unhandledMessage(msg); 976 break; 977 } 978 if (mDbg) { 979 mSm.log("processMsg: " + curStateInfo.state.getName()); 980 } 981 } 982 } 983 return (curStateInfo != null) ? curStateInfo.state : null; 984 } 985 986 /** 987 * Call the exit method for each state from the top of stack 988 * up to the common ancestor state. 989 */ 990 private final void invokeExitMethods(StateInfo commonStateInfo) { 991 while ((mStateStackTopIndex >= 0) && 992 (mStateStack[mStateStackTopIndex] != commonStateInfo)) { 993 State curState = mStateStack[mStateStackTopIndex].state; 994 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName()); 995 curState.exit(); 996 mStateStack[mStateStackTopIndex].active = false; 997 mStateStackTopIndex -= 1; 998 } 999 } 1000 1001 /** 1002 * Invoke the enter method starting at the entering index to top of state stack 1003 */ 1004 private final void invokeEnterMethods(int stateStackEnteringIndex) { 1005 for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { 1006 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName()); 1007 mStateStack[i].state.enter(); 1008 mStateStack[i].active = true; 1009 } 1010 } 1011 1012 /** 1013 * Move the deferred message to the front of the message queue. 1014 */ 1015 private final void moveDeferredMessageAtFrontOfQueue() { 1016 /** 1017 * The oldest messages on the deferred list must be at 1018 * the front of the queue so start at the back, which 1019 * as the most resent message and end with the oldest 1020 * messages at the front of the queue. 1021 */ 1022 for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) { 1023 Message curMsg = mDeferredMessages.get(i); 1024 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what); 1025 sendMessageAtFrontOfQueue(curMsg); 1026 } 1027 mDeferredMessages.clear(); 1028 } 1029 1030 /** 1031 * Move the contents of the temporary stack to the state stack 1032 * reversing the order of the items on the temporary stack as 1033 * they are moved. 1034 * 1035 * @return index into mStateStack where entering needs to start 1036 */ 1037 private final int moveTempStateStackToStateStack() { 1038 int startingIndex = mStateStackTopIndex + 1; 1039 int i = mTempStateStackCount - 1; 1040 int j = startingIndex; 1041 while (i >= 0) { 1042 if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j); 1043 mStateStack[j] = mTempStateStack[i]; 1044 j += 1; 1045 i -= 1; 1046 } 1047 1048 mStateStackTopIndex = j - 1; 1049 if (mDbg) { 1050 mSm.log("moveTempStackToStateStack: X mStateStackTop=" 1051 + mStateStackTopIndex + ",startingIndex=" + startingIndex 1052 + ",Top=" + mStateStack[mStateStackTopIndex].state.getName()); 1053 } 1054 return startingIndex; 1055 } 1056 1057 /** 1058 * Setup the mTempStateStack with the states we are going to enter. 1059 * 1060 * This is found by searching up the destState's ancestors for a 1061 * state that is already active i.e. StateInfo.active == true. 1062 * The destStae and all of its inactive parents will be on the 1063 * TempStateStack as the list of states to enter. 1064 * 1065 * @return StateInfo of the common ancestor for the destState and 1066 * current state or null if there is no common parent. 1067 */ 1068 private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { 1069 /** 1070 * Search up the parent list of the destination state for an active 1071 * state. Use a do while() loop as the destState must always be entered 1072 * even if it is active. This can happen if we are exiting/entering 1073 * the current state. 1074 */ 1075 mTempStateStackCount = 0; 1076 StateInfo curStateInfo = mStateInfo.get(destState); 1077 do { 1078 mTempStateStack[mTempStateStackCount++] = curStateInfo; 1079 curStateInfo = curStateInfo.parentStateInfo; 1080 } while ((curStateInfo != null) && !curStateInfo.active); 1081 1082 if (mDbg) { 1083 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount=" 1084 + mTempStateStackCount + ",curStateInfo: " + curStateInfo); 1085 } 1086 return curStateInfo; 1087 } 1088 1089 /** 1090 * Initialize StateStack to mInitialState. 1091 */ 1092 private final void setupInitialStateStack() { 1093 if (mDbg) { 1094 mSm.log("setupInitialStateStack: E mInitialState=" 1095 + mInitialState.getName()); 1096 } 1097 1098 StateInfo curStateInfo = mStateInfo.get(mInitialState); 1099 for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { 1100 mTempStateStack[mTempStateStackCount] = curStateInfo; 1101 curStateInfo = curStateInfo.parentStateInfo; 1102 } 1103 1104 // Empty the StateStack 1105 mStateStackTopIndex = -1; 1106 1107 moveTempStateStackToStateStack(); 1108 } 1109 1110 /** 1111 * @return current message 1112 */ 1113 private final Message getCurrentMessage() { 1114 return mMsg; 1115 } 1116 1117 /** 1118 * @return current state 1119 */ 1120 private final IState getCurrentState() { 1121 return mStateStack[mStateStackTopIndex].state; 1122 } 1123 1124 /** 1125 * Add a new state to the state machine. Bottom up addition 1126 * of states is allowed but the same state may only exist 1127 * in one hierarchy. 1128 * 1129 * @param state the state to add 1130 * @param parent the parent of state 1131 * @return stateInfo for this state 1132 */ 1133 private final StateInfo addState(State state, State parent) { 1134 if (mDbg) { 1135 mSm.log("addStateInternal: E state=" + state.getName() 1136 + ",parent=" + ((parent == null) ? "" : parent.getName())); 1137 } 1138 StateInfo parentStateInfo = null; 1139 if (parent != null) { 1140 parentStateInfo = mStateInfo.get(parent); 1141 if (parentStateInfo == null) { 1142 // Recursively add our parent as it's not been added yet. 1143 parentStateInfo = addState(parent, null); 1144 } 1145 } 1146 StateInfo stateInfo = mStateInfo.get(state); 1147 if (stateInfo == null) { 1148 stateInfo = new StateInfo(); 1149 mStateInfo.put(state, stateInfo); 1150 } 1151 1152 // Validate that we aren't adding the same state in two different hierarchies. 1153 if ((stateInfo.parentStateInfo != null) && 1154 (stateInfo.parentStateInfo != parentStateInfo)) { 1155 throw new RuntimeException("state already added"); 1156 } 1157 stateInfo.state = state; 1158 stateInfo.parentStateInfo = parentStateInfo; 1159 stateInfo.active = false; 1160 if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo); 1161 return stateInfo; 1162 } 1163 1164 /** 1165 * Constructor 1166 * 1167 * @param looper for dispatching messages 1168 * @param sm the hierarchical state machine 1169 */ 1170 private SmHandler(Looper looper, StateMachine sm) { 1171 super(looper); 1172 mSm = sm; 1173 1174 addState(mHaltingState, null); 1175 addState(mQuittingState, null); 1176 } 1177 1178 /** @see StateMachine#setInitialState(State) */ 1179 private final void setInitialState(State initialState) { 1180 if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName()); 1181 mInitialState = initialState; 1182 } 1183 1184 /** @see StateMachine#transitionTo(IState) */ 1185 private final void transitionTo(IState destState) { 1186 mDestState = (State) destState; 1187 if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName()); 1188 } 1189 1190 /** @see StateMachine#deferMessage(Message) */ 1191 private final void deferMessage(Message msg) { 1192 if (mDbg) mSm.log("deferMessage: msg=" + msg.what); 1193 1194 /* Copy the "msg" to "newMsg" as "msg" will be recycled */ 1195 Message newMsg = obtainMessage(); 1196 newMsg.copyFrom(msg); 1197 1198 mDeferredMessages.add(newMsg); 1199 } 1200 1201 /** @see StateMachine#quit() */ 1202 private final void quit() { 1203 if (mDbg) mSm.log("quit:"); 1204 sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); 1205 } 1206 1207 /** @see StateMachine#quitNow() */ 1208 private final void quitNow() { 1209 if (mDbg) mSm.log("quitNow:"); 1210 sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); 1211 } 1212 1213 /** Validate that the message was sent by quit or quitNow. */ 1214 private final boolean isQuit(Message msg) { 1215 return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj); 1216 } 1217 1218 /** @see StateMachine#isDbg() */ 1219 private final boolean isDbg() { 1220 return mDbg; 1221 } 1222 1223 /** @see StateMachine#setDbg(boolean) */ 1224 private final void setDbg(boolean dbg) { 1225 mDbg = dbg; 1226 } 1227 1228 } 1229 1230 private SmHandler mSmHandler; 1231 private HandlerThread mSmThread; 1232 1233 /** 1234 * Initialize. 1235 * 1236 * @param looper for this state machine 1237 * @param name of the state machine 1238 */ 1239 private void initStateMachine(String name, Looper looper) { 1240 mName = name; 1241 mSmHandler = new SmHandler(looper, this); 1242 } 1243 1244 /** 1245 * Constructor creates a StateMachine with its own thread. 1246 * 1247 * @param name of the state machine 1248 */ 1249 protected StateMachine(String name) { 1250 mSmThread = new HandlerThread(name); 1251 mSmThread.start(); 1252 Looper looper = mSmThread.getLooper(); 1253 1254 initStateMachine(name, looper); 1255 } 1256 1257 /** 1258 * Constructor creates a StateMachine using the looper. 1259 * 1260 * @param name of the state machine 1261 */ 1262 protected StateMachine(String name, Looper looper) { 1263 initStateMachine(name, looper); 1264 } 1265 1266 /** 1267 * Add a new state to the state machine 1268 * @param state the state to add 1269 * @param parent the parent of state 1270 */ 1271 protected final void addState(State state, State parent) { 1272 mSmHandler.addState(state, parent); 1273 } 1274 1275 /** 1276 * Add a new state to the state machine, parent will be null 1277 * @param state to add 1278 */ 1279 protected final void addState(State state) { 1280 mSmHandler.addState(state, null); 1281 } 1282 1283 /** 1284 * Set the initial state. This must be invoked before 1285 * and messages are sent to the state machine. 1286 * 1287 * @param initialState is the state which will receive the first message. 1288 */ 1289 protected final void setInitialState(State initialState) { 1290 mSmHandler.setInitialState(initialState); 1291 } 1292 1293 /** 1294 * @return current message 1295 */ 1296 protected final Message getCurrentMessage() { 1297 // mSmHandler can be null if the state machine has quit. 1298 SmHandler smh = mSmHandler; 1299 if (smh == null) return null; 1300 return smh.getCurrentMessage(); 1301 } 1302 1303 /** 1304 * @return current state 1305 */ 1306 protected final IState getCurrentState() { 1307 // mSmHandler can be null if the state machine has quit. 1308 SmHandler smh = mSmHandler; 1309 if (smh == null) return null; 1310 return smh.getCurrentState(); 1311 } 1312 1313 /** 1314 * transition to destination state. Upon returning 1315 * from processMessage the current state's exit will 1316 * be executed and upon the next message arriving 1317 * destState.enter will be invoked. 1318 * 1319 * this function can also be called inside the enter function of the 1320 * previous transition target, but the behavior is undefined when it is 1321 * called mid-way through a previous transition (for example, calling this 1322 * in the enter() routine of a intermediate node when the current transition 1323 * target is one of the nodes descendants). 1324 * 1325 * @param destState will be the state that receives the next message. 1326 */ 1327 protected final void transitionTo(IState destState) { 1328 mSmHandler.transitionTo(destState); 1329 } 1330 1331 /** 1332 * transition to halt state. Upon returning 1333 * from processMessage we will exit all current 1334 * states, execute the onHalting() method and then 1335 * for all subsequent messages haltedProcessMessage 1336 * will be called. 1337 */ 1338 protected final void transitionToHaltingState() { 1339 mSmHandler.transitionTo(mSmHandler.mHaltingState); 1340 } 1341 1342 /** 1343 * Defer this message until next state transition. 1344 * Upon transitioning all deferred messages will be 1345 * placed on the queue and reprocessed in the original 1346 * order. (i.e. The next state the oldest messages will 1347 * be processed first) 1348 * 1349 * @param msg is deferred until the next transition. 1350 */ 1351 protected final void deferMessage(Message msg) { 1352 mSmHandler.deferMessage(msg); 1353 } 1354 1355 /** 1356 * Called when message wasn't handled 1357 * 1358 * @param msg that couldn't be handled. 1359 */ 1360 protected void unhandledMessage(Message msg) { 1361 if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what); 1362 } 1363 1364 /** 1365 * Called for any message that is received after 1366 * transitionToHalting is called. 1367 */ 1368 protected void haltedProcessMessage(Message msg) { 1369 } 1370 1371 /** 1372 * This will be called once after handling a message that called 1373 * transitionToHalting. All subsequent messages will invoke 1374 * {@link StateMachine#haltedProcessMessage(Message)} 1375 */ 1376 protected void onHalting() { 1377 } 1378 1379 /** 1380 * This will be called once after a quit message that was NOT handled by 1381 * the derived StateMachine. The StateMachine will stop and any subsequent messages will be 1382 * ignored. In addition, if this StateMachine created the thread, the thread will 1383 * be stopped after this method returns. 1384 */ 1385 protected void onQuitting() { 1386 } 1387 1388 /** 1389 * @return the name 1390 */ 1391 public final String getName() { 1392 return mName; 1393 } 1394 1395 /** 1396 * Set number of log records to maintain and clears all current records. 1397 * 1398 * @param maxSize number of messages to maintain at anyone time. 1399 */ 1400 public final void setLogRecSize(int maxSize) { 1401 mSmHandler.mLogRecords.setSize(maxSize); 1402 } 1403 1404 /** 1405 * Set to log only messages that cause a state transition 1406 * 1407 * @param enable {@code true} to enable, {@code false} to disable 1408 */ 1409 public final void setLogOnlyTransitions(boolean enable) { 1410 mSmHandler.mLogRecords.setLogOnlyTransitions(enable); 1411 } 1412 1413 /** 1414 * @return number of log records 1415 */ 1416 public final int getLogRecSize() { 1417 // mSmHandler can be null if the state machine has quit. 1418 SmHandler smh = mSmHandler; 1419 if (smh == null) return 0; 1420 return smh.mLogRecords.size(); 1421 } 1422 1423 /** 1424 * @return the total number of records processed 1425 */ 1426 public final int getLogRecCount() { 1427 // mSmHandler can be null if the state machine has quit. 1428 SmHandler smh = mSmHandler; 1429 if (smh == null) return 0; 1430 return smh.mLogRecords.count(); 1431 } 1432 1433 /** 1434 * @return a log record, or null if index is out of range 1435 */ 1436 public final LogRec getLogRec(int index) { 1437 // mSmHandler can be null if the state machine has quit. 1438 SmHandler smh = mSmHandler; 1439 if (smh == null) return null; 1440 return smh.mLogRecords.get(index); 1441 } 1442 1443 /** 1444 * @return a copy of LogRecs as a collection 1445 */ 1446 public final Collection<LogRec> copyLogRecs() { 1447 Vector<LogRec> vlr = new Vector<LogRec>(); 1448 SmHandler smh = mSmHandler; 1449 if (smh != null) { 1450 for (LogRec lr : smh.mLogRecords.mLogRecVector) { 1451 vlr.add(lr); 1452 } 1453 } 1454 return vlr; 1455 } 1456 1457 /** 1458 * Add the string to LogRecords. 1459 * 1460 * @param string 1461 */ 1462 protected void addLogRec(String string) { 1463 // mSmHandler can be null if the state machine has quit. 1464 SmHandler smh = mSmHandler; 1465 if (smh == null) return; 1466 smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(), 1467 smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState); 1468 } 1469 1470 /** 1471 * @return true if msg should be saved in the log, default is true. 1472 */ 1473 protected boolean recordLogRec(Message msg) { 1474 return true; 1475 } 1476 1477 /** 1478 * Return a string to be logged by LogRec, default 1479 * is an empty string. Override if additional information is desired. 1480 * 1481 * @param msg that was processed 1482 * @return information to be logged as a String 1483 */ 1484 protected String getLogRecString(Message msg) { 1485 return ""; 1486 } 1487 1488 /** 1489 * @return the string for msg.what 1490 */ 1491 protected String getWhatToString(int what) { 1492 return null; 1493 } 1494 1495 /** 1496 * @return Handler, maybe null if state machine has quit. 1497 */ 1498 public final Handler getHandler() { 1499 return mSmHandler; 1500 } 1501 1502 /** 1503 * Get a message and set Message.target state machine handler. 1504 * 1505 * Note: The handler can be null if the state machine has quit, 1506 * which means target will be null and may cause a AndroidRuntimeException 1507 * in MessageQueue#enqueMessage if sent directly or if sent using 1508 * StateMachine#sendMessage the message will just be ignored. 1509 * 1510 * @return A Message object from the global pool 1511 */ 1512 public final Message obtainMessage() 1513 { 1514 return Message.obtain(mSmHandler); 1515 } 1516 1517 /** 1518 * Get a message and set Message.target state machine handler, what. 1519 * 1520 * Note: The handler can be null if the state machine has quit, 1521 * which means target will be null and may cause a AndroidRuntimeException 1522 * in MessageQueue#enqueMessage if sent directly or if sent using 1523 * StateMachine#sendMessage the message will just be ignored. 1524 * 1525 * @param what is the assigned to Message.what. 1526 * @return A Message object from the global pool 1527 */ 1528 public final Message obtainMessage(int what) { 1529 return Message.obtain(mSmHandler, what); 1530 } 1531 1532 /** 1533 * Get a message and set Message.target state machine handler, 1534 * what and obj. 1535 * 1536 * Note: The handler can be null if the state machine has quit, 1537 * which means target will be null and may cause a AndroidRuntimeException 1538 * in MessageQueue#enqueMessage if sent directly or if sent using 1539 * StateMachine#sendMessage the message will just be ignored. 1540 * 1541 * @param what is the assigned to Message.what. 1542 * @param obj is assigned to Message.obj. 1543 * @return A Message object from the global pool 1544 */ 1545 public final Message obtainMessage(int what, Object obj) 1546 { 1547 return Message.obtain(mSmHandler, what, obj); 1548 } 1549 1550 /** 1551 * Get a message and set Message.target state machine handler, 1552 * what, arg1 and arg2 1553 * 1554 * Note: The handler can be null if the state machine has quit, 1555 * which means target will be null and may cause a AndroidRuntimeException 1556 * in MessageQueue#enqueMessage if sent directly or if sent using 1557 * StateMachine#sendMessage the message will just be ignored. 1558 * 1559 * @param what is assigned to Message.what 1560 * @param arg1 is assigned to Message.arg1 1561 * @param arg2 is assigned to Message.arg2 1562 * @return A Message object from the global pool 1563 */ 1564 public final Message obtainMessage(int what, int arg1, int arg2) 1565 { 1566 return Message.obtain(mSmHandler, what, arg1, arg2); 1567 } 1568 1569 /** 1570 * Get a message and set Message.target state machine handler, 1571 * what, arg1, arg2 and obj 1572 * 1573 * Note: The handler can be null if the state machine has quit, 1574 * which means target will be null and may cause a AndroidRuntimeException 1575 * in MessageQueue#enqueMessage if sent directly or if sent using 1576 * StateMachine#sendMessage the message will just be ignored. 1577 * 1578 * @param what is assigned to Message.what 1579 * @param arg1 is assigned to Message.arg1 1580 * @param arg2 is assigned to Message.arg2 1581 * @param obj is assigned to Message.obj 1582 * @return A Message object from the global pool 1583 */ 1584 public final Message obtainMessage(int what, int arg1, int arg2, Object obj) 1585 { 1586 return Message.obtain(mSmHandler, what, arg1, arg2, obj); 1587 } 1588 1589 /** 1590 * Enqueue a message to this state machine. 1591 * 1592 * Message is ignored if state machine has quit. 1593 */ 1594 public final void sendMessage(int what) { 1595 // mSmHandler can be null if the state machine has quit. 1596 SmHandler smh = mSmHandler; 1597 if (smh == null) return; 1598 1599 smh.sendMessage(obtainMessage(what)); 1600 } 1601 1602 /** 1603 * Enqueue a message to this state machine. 1604 * 1605 * Message is ignored if state machine has quit. 1606 */ 1607 public final void sendMessage(int what, Object obj) { 1608 // mSmHandler can be null if the state machine has quit. 1609 SmHandler smh = mSmHandler; 1610 if (smh == null) return; 1611 1612 smh.sendMessage(obtainMessage(what,obj)); 1613 } 1614 1615 /** 1616 * Enqueue a message to this state machine. 1617 * 1618 * Message is ignored if state machine has quit. 1619 */ 1620 public final void sendMessage(Message msg) { 1621 // mSmHandler can be null if the state machine has quit. 1622 SmHandler smh = mSmHandler; 1623 if (smh == null) return; 1624 1625 smh.sendMessage(msg); 1626 } 1627 1628 /** 1629 * Enqueue a message to this state machine after a delay. 1630 * 1631 * Message is ignored if state machine has quit. 1632 */ 1633 public final void sendMessageDelayed(int what, long delayMillis) { 1634 // mSmHandler can be null if the state machine has quit. 1635 SmHandler smh = mSmHandler; 1636 if (smh == null) return; 1637 1638 smh.sendMessageDelayed(obtainMessage(what), delayMillis); 1639 } 1640 1641 /** 1642 * Enqueue a message to this state machine after a delay. 1643 * 1644 * Message is ignored if state machine has quit. 1645 */ 1646 public final void sendMessageDelayed(int what, Object obj, long delayMillis) { 1647 // mSmHandler can be null if the state machine has quit. 1648 SmHandler smh = mSmHandler; 1649 if (smh == null) return; 1650 1651 smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis); 1652 } 1653 1654 /** 1655 * Enqueue a message to this state machine after a delay. 1656 * 1657 * Message is ignored if state machine has quit. 1658 */ 1659 public final void sendMessageDelayed(Message msg, long delayMillis) { 1660 // mSmHandler can be null if the state machine has quit. 1661 SmHandler smh = mSmHandler; 1662 if (smh == null) return; 1663 1664 smh.sendMessageDelayed(msg, delayMillis); 1665 } 1666 1667 /** 1668 * Enqueue a message to the front of the queue for this state machine. 1669 * Protected, may only be called by instances of StateMachine. 1670 * 1671 * Message is ignored if state machine has quit. 1672 */ 1673 protected final void sendMessageAtFrontOfQueue(int what, Object obj) { 1674 // mSmHandler can be null if the state machine has quit. 1675 SmHandler smh = mSmHandler; 1676 if (smh == null) return; 1677 1678 smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj)); 1679 } 1680 1681 /** 1682 * Enqueue a message to the front of the queue for this state machine. 1683 * Protected, may only be called by instances of StateMachine. 1684 * 1685 * Message is ignored if state machine has quit. 1686 */ 1687 protected final void sendMessageAtFrontOfQueue(int what) { 1688 // mSmHandler can be null if the state machine has quit. 1689 SmHandler smh = mSmHandler; 1690 if (smh == null) return; 1691 1692 smh.sendMessageAtFrontOfQueue(obtainMessage(what)); 1693 } 1694 1695 /** 1696 * Enqueue a message to the front of the queue for this state machine. 1697 * Protected, may only be called by instances of StateMachine. 1698 * 1699 * Message is ignored if state machine has quit. 1700 */ 1701 protected final void sendMessageAtFrontOfQueue(Message msg) { 1702 // mSmHandler can be null if the state machine has quit. 1703 SmHandler smh = mSmHandler; 1704 if (smh == null) return; 1705 1706 smh.sendMessageAtFrontOfQueue(msg); 1707 } 1708 1709 /** 1710 * Removes a message from the message queue. 1711 * Protected, may only be called by instances of StateMachine. 1712 */ 1713 protected final void removeMessages(int what) { 1714 // mSmHandler can be null if the state machine has quit. 1715 SmHandler smh = mSmHandler; 1716 if (smh == null) return; 1717 1718 smh.removeMessages(what); 1719 } 1720 1721 /** 1722 * Validate that the message was sent by 1723 * {@link StateMachine#quit} or {@link StateMachine#quitNow}. 1724 * */ 1725 protected final boolean isQuit(Message msg) { 1726 // mSmHandler can be null if the state machine has quit. 1727 SmHandler smh = mSmHandler; 1728 if (smh == null) return msg.what == SM_QUIT_CMD; 1729 1730 return smh.isQuit(msg); 1731 } 1732 1733 /** 1734 * Quit the state machine after all currently queued up messages are processed. 1735 */ 1736 protected final void quit() { 1737 // mSmHandler can be null if the state machine is already stopped. 1738 SmHandler smh = mSmHandler; 1739 if (smh == null) return; 1740 1741 smh.quit(); 1742 } 1743 1744 /** 1745 * Quit the state machine immediately all currently queued messages will be discarded. 1746 */ 1747 protected final void quitNow() { 1748 // mSmHandler can be null if the state machine is already stopped. 1749 SmHandler smh = mSmHandler; 1750 if (smh == null) return; 1751 1752 smh.quitNow(); 1753 } 1754 1755 /** 1756 * @return if debugging is enabled 1757 */ 1758 public boolean isDbg() { 1759 // mSmHandler can be null if the state machine has quit. 1760 SmHandler smh = mSmHandler; 1761 if (smh == null) return false; 1762 1763 return smh.isDbg(); 1764 } 1765 1766 /** 1767 * Set debug enable/disabled. 1768 * 1769 * @param dbg is true to enable debugging. 1770 */ 1771 public void setDbg(boolean dbg) { 1772 // mSmHandler can be null if the state machine has quit. 1773 SmHandler smh = mSmHandler; 1774 if (smh == null) return; 1775 1776 smh.setDbg(dbg); 1777 } 1778 1779 /** 1780 * Start the state machine. 1781 */ 1782 public void start() { 1783 // mSmHandler can be null if the state machine has quit. 1784 SmHandler smh = mSmHandler; 1785 if (smh == null) return; 1786 1787 /** Send the complete construction message */ 1788 smh.completeConstruction(); 1789 } 1790 1791 /** 1792 * Dump the current state. 1793 * 1794 * @param fd 1795 * @param pw 1796 * @param args 1797 */ 1798 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1799 pw.println(getName() + ":"); 1800 pw.println(" total records=" + getLogRecCount()); 1801 for (int i=0; i < getLogRecSize(); i++) { 1802 pw.printf(" rec[%d]: %s\n", i, getLogRec(i).toString()); 1803 pw.flush(); 1804 } 1805 pw.println("curState=" + getCurrentState().getName()); 1806 } 1807 1808 protected void log(String s) { 1809 Log.d(mName, s); 1810 } 1811 1812 protected void logv(String s) { 1813 Log.v(mName, s); 1814 } 1815 1816 protected void logi(String s) { 1817 Log.i(mName, s); 1818 } 1819 1820 protected void logd(String s) { 1821 Log.d(mName, s); 1822 } 1823 1824 protected void logw(String s) { 1825 Log.w(mName, s); 1826 } 1827 1828 protected void loge(String s) { 1829 Log.e(mName, s); 1830 } 1831} 1832