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