SyncSM8.1 always call transitionTo under processMessage

As state transitions always occur after a message has been processed,
calling transitionTo only under processMessage makes the flow easier
to understand and less error-prone.

The risk of this change is very minimal since it is only about
handling the error, but tethering will stop regardless.

Test: atest TetheringTests

Change-Id: I56c6cf6cc989464ee84a8333ac131afc808a3d95
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 6063526..c04d0dc 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -237,6 +237,7 @@
     public static final int CMD_NEW_PREFIX_REQUEST          = BASE_IPSERVER + 12;
     // request from PrivateAddressCoordinator to restart tethering.
     public static final int CMD_NOTIFY_PREFIX_CONFLICT      = BASE_IPSERVER + 13;
+    public static final int CMD_SERVICE_FAILED_TO_START     = BASE_IPSERVER + 14;
 
     private final State mInitialState;
     private final State mLocalHotspotState;
@@ -510,7 +511,8 @@
 
         private void handleError() {
             mLastError = TETHER_ERROR_DHCPSERVER_ERROR;
-            transitionTo(mInitialState);
+            sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START,
+                    TETHER_ERROR_DHCPSERVER_ERROR);
         }
     }
 
@@ -1120,7 +1122,14 @@
             startServingInterface();
 
             if (mLastError != TETHER_ERROR_NO_ERROR) {
-                transitionTo(mInitialState);
+                // This will transition to InitialState right away, regardless of whether any
+                // message is already waiting in the StateMachine queue (including maybe some
+                // message to go to InitialState). InitialState will then process any pending
+                // message (and generally ignores them). It is difficult to know for sure whether
+                // this is correct in all cases, but this is equivalent to what IpServer was doing
+                // in previous versions of the mainline module.
+                // TODO : remove this after migrating to the Sync StateMachine.
+                sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START, mLastError);
             }
 
             if (DBG) Log.d(TAG, getStateString(mDesiredInterfaceState) + " serve " + mIfaceName);
@@ -1210,6 +1219,9 @@
                     mCallback.requestEnableTethering(mInterfaceType, false /* enabled */);
                     transitionTo(mWaitingForRestartState);
                     break;
+                case CMD_SERVICE_FAILED_TO_START:
+                    mLog.e("start serving fail, error: " + message.arg1);
+                    transitionTo(mInitialState);
                 default:
                     return false;
             }
diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
index fc432f7..078a35f 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
@@ -29,7 +29,7 @@
 /** A wrapper to decide whether use synchronous state machine for tethering. */
 public class StateMachineShim {
     // Exactly one of mAsyncSM or mSyncSM is non-null.
-    private final StateMachine mAsyncSM;
+    private final AsyncStateMachine mAsyncSM;
     private final SyncStateMachine mSyncSM;
 
     /**
@@ -149,6 +149,21 @@
     }
 
     /**
+     * Enqueue a message to the front of the queue.
+     * Protected, may only be called by instances of async state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected void sendMessageAtFrontOfQueueToAsyncSM(int what, int arg1) {
+        if (mSyncSM != null) {
+            throw new IllegalStateException("sendMessageAtFrontOfQueue can only be used with"
+                    + " async SM");
+        }
+
+        mAsyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1);
+    }
+
+    /**
      * Send self message.
      * This can only be used with sync state machine, so this will throw if using async state
      * machine.
@@ -172,5 +187,10 @@
         public AsyncStateMachine(final String name, final Looper looper) {
             super(name, looper);
         }
+
+        /** Enqueue a message to the front of the queue for this state machine. */
+        public void sendMessageAtFrontOfQueueToAsyncSM(int what, int arg1) {
+            sendMessageAtFrontOfQueue(what, arg1);
+        }
     }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
index 2c4df76..f8e98e3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
@@ -78,6 +78,10 @@
             shimUsingSyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
         }
 
+        assertFailsWith(IllegalStateException::class) {
+            shimUsingSyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1)
+        }
+
         shimUsingSyncSM.sendSelfMessageToSyncSM(what, obj)
         inOrder.verify(mSyncSM).sendSelfMessage(what, 0, 0, obj)
 
@@ -119,6 +123,9 @@
         shimUsingAsyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
         inOrder.verify(mAsyncSM).sendMessageDelayed(what, 1000)
 
+        shimUsingAsyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1)
+        inOrder.verify(mAsyncSM).sendMessageAtFrontOfQueueToAsyncSM(what, arg1)
+
         assertFailsWith(IllegalStateException::class) {
             shimUsingAsyncSM.sendSelfMessageToSyncSM(what, obj)
         }