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