Merge changes I34903b57,Ic818aa55 into main

* changes:
  SyncSM07: Replace IpServer's state machine with StateMachineShim
  SyncSM06: Add StateMachineShim
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 246e5bc..6063526 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -33,6 +33,7 @@
 
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
+import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
 import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
 import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
 import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
@@ -56,7 +57,6 @@
 import android.net.dhcp.IDhcpServer;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -68,7 +68,6 @@
 
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetdUtils;
@@ -85,6 +84,8 @@
 import com.android.networkstack.tethering.metrics.TetheringMetrics;
 import com.android.networkstack.tethering.util.InterfaceSet;
 import com.android.networkstack.tethering.util.PrefixUtils;
+import com.android.networkstack.tethering.util.StateMachineShim;
+import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -104,7 +105,7 @@
  *
  * @hide
  */
-public class IpServer extends StateMachine {
+public class IpServer extends StateMachineShim {
     public static final int STATE_UNAVAILABLE = 0;
     public static final int STATE_AVAILABLE   = 1;
     public static final int STATE_TETHERED    = 2;
@@ -309,16 +310,18 @@
     private LinkAddress mIpv4Address;
 
     private final TetheringMetrics mTetheringMetrics;
+    private final Handler mHandler;
 
     // TODO: Add a dependency object to pass the data members or variables from the tethering
     // object. It helps to reduce the arguments of the constructor.
     public IpServer(
-            String ifaceName, Looper looper, int interfaceType, SharedLog log,
+            String ifaceName, Handler handler, int interfaceType, SharedLog log,
             INetd netd, @NonNull BpfCoordinator bpfCoordinator,
             @Nullable LateSdk<RoutingCoordinatorManager> routingCoordinator, Callback callback,
             TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
             TetheringMetrics tetheringMetrics, Dependencies deps) {
-        super(ifaceName, looper);
+        super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
+        mHandler = handler;
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
         mBpfCoordinator = bpfCoordinator;
@@ -351,13 +354,22 @@
         mTetheredState = new TetheredState();
         mUnavailableState = new UnavailableState();
         mWaitingForRestartState = new WaitingForRestartState();
-        addState(mInitialState);
-        addState(mLocalHotspotState);
-        addState(mTetheredState);
-        addState(mWaitingForRestartState, mTetheredState);
-        addState(mUnavailableState);
+        final ArrayList allStates = new ArrayList<StateInfo>();
+        allStates.add(new StateInfo(mInitialState, null));
+        allStates.add(new StateInfo(mLocalHotspotState, null));
+        allStates.add(new StateInfo(mTetheredState, null));
+        allStates.add(new StateInfo(mWaitingForRestartState, mTetheredState));
+        allStates.add(new StateInfo(mUnavailableState, null));
+        addAllStates(allStates);
+    }
 
-        setInitialState(mInitialState);
+    private Handler getHandler() {
+        return mHandler;
+    }
+
+    /** Start IpServer state machine. */
+    public void start() {
+        start(mInitialState);
     }
 
     /** Interface name which IpServer served.*/
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index f52bed9..b7607ef 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2841,7 +2841,7 @@
 
         mLog.i("adding IpServer for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
+                new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
                         mRoutingCoordinator, makeControlCallback(), mConfig,
                         mPrivateAddressCoordinator, mTetheringMetrics,
                         mDeps.getIpServerDependencies()), isNcm);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 747cc20..502fee8 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -136,6 +136,9 @@
      */
     public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000;
 
+    /** A flag for using synchronous or asynchronous state machine. */
+    public static final boolean USE_SYNC_SM = false;
+
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
     public final String[] tetherableWigigRegexs;
diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
new file mode 100644
index 0000000..fc432f7
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering.util;
+
+import android.annotation.Nullable;
+import android.os.Looper;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
+
+import java.util.List;
+
+/** 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 SyncStateMachine mSyncSM;
+
+    /**
+     * The Looper parameter is only needed for AsyncSM, so if looper is null, the shim will be
+     * created for SyncSM.
+     */
+    public StateMachineShim(final String name, @Nullable final Looper looper) {
+        this(name, looper, new Dependencies());
+    }
+
+    @VisibleForTesting
+    public StateMachineShim(final String name, @Nullable final Looper looper,
+            final Dependencies deps) {
+        if (looper == null) {
+            mAsyncSM = null;
+            mSyncSM = deps.makeSyncStateMachine(name, Thread.currentThread());
+        } else {
+            mAsyncSM = deps.makeAsyncStateMachine(name, looper);
+            mSyncSM = null;
+        }
+    }
+
+    /** A dependencies class which used for testing injection. */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Create SyncSM instance, for injection. */
+        public SyncStateMachine makeSyncStateMachine(final String name, final Thread thread) {
+            return new SyncStateMachine(name, thread);
+        }
+
+        /** Create AsyncSM instance, for injection. */
+        public AsyncStateMachine makeAsyncStateMachine(final String name, final Looper looper) {
+            return new AsyncStateMachine(name, looper);
+        }
+    }
+
+    /** Start the state machine */
+    public void start(final State initialState) {
+        if (mSyncSM != null) {
+            mSyncSM.start(initialState);
+        } else {
+            mAsyncSM.setInitialState(initialState);
+            mAsyncSM.start();
+        }
+    }
+
+    /** Add states to state machine. */
+    public void addAllStates(final List<StateInfo> stateInfos) {
+        if (mSyncSM != null) {
+            mSyncSM.addAllStates(stateInfos);
+        } else {
+            for (final StateInfo info : stateInfos) {
+                mAsyncSM.addState(info.state, info.parent);
+            }
+        }
+    }
+
+    /**
+     * Transition to given state.
+     *
+     * SyncSM doesn't allow this be called during state transition (#enter() or #exit() methods),
+     * or multiple times while processing a single message.
+     */
+    public void transitionTo(final State state) {
+        if (mSyncSM != null) {
+            mSyncSM.transitionTo(state);
+        } else {
+            mAsyncSM.transitionTo(state);
+        }
+    }
+
+    /** Send message to state machine. */
+    public void sendMessage(int what) {
+        sendMessage(what, 0, 0, null);
+    }
+
+    /** Send message to state machine. */
+    public void sendMessage(int what, Object obj) {
+        sendMessage(what, 0, 0, obj);
+    }
+
+    /** Send message to state machine. */
+    public void sendMessage(int what, int arg1) {
+        sendMessage(what, arg1, 0, null);
+    }
+
+    /**
+     * Send message to state machine.
+     *
+     * If using asynchronous state machine, putting the message into looper's message queue.
+     * Tethering runs on single looper thread that ipServers and mainSM all share with same message
+     * queue. The enqueued message will be processed by asynchronous state machine when all the
+     * messages before such enqueued message are processed.
+     * If using synchronous state machine, the message is processed right away without putting into
+     * looper's message queue.
+     */
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
+        if (mSyncSM != null) {
+            mSyncSM.processMessage(what, arg1, arg2, obj);
+        } else {
+            mAsyncSM.sendMessage(what, arg1, arg2, obj);
+        }
+    }
+
+    /**
+     * Send message after delayMillis millisecond.
+     *
+     * This can only be used with async state machine, so this will throw if using sync state
+     * machine.
+     */
+    public void sendMessageDelayedToAsyncSM(final int what, final long delayMillis) {
+        if (mSyncSM != null) {
+            throw new IllegalStateException("sendMessageDelayed can only be used with async SM");
+        }
+
+        mAsyncSM.sendMessageDelayed(what, delayMillis);
+    }
+
+    /**
+     * Send self message.
+     * This can only be used with sync state machine, so this will throw if using async state
+     * machine.
+     */
+    public void sendSelfMessageToSyncSM(final int what, final Object obj) {
+        if (mSyncSM == null) {
+            throw new IllegalStateException("sendSelfMessage can only be used with sync SM");
+        }
+
+        mSyncSM.sendSelfMessage(what, 0, 0, obj);
+    }
+
+    /**
+     * An alias StateMahchine class with public construtor.
+     *
+     * Since StateMachine.java only provides protected construtor, adding a child class so that this
+     * shim could create StateMachine instance.
+     */
+    @VisibleForTesting
+    public static class AsyncStateMachine extends StateMachine {
+        public AsyncStateMachine(final String name, final Looper looper) {
+            super(name, looper);
+        }
+    }
+}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index fc9928d..d945025 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -215,6 +215,7 @@
     @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
 
     private final TestLooper mLooper = new TestLooper();
+    private final Handler mHandler = new Handler(mLooper.getLooper());
     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
             ArgumentCaptor.forClass(LinkProperties.class);
     private IpServer mIpServer;
@@ -254,7 +255,7 @@
         // Recreate mBpfCoordinator again here because mTetherConfig has changed
         mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
         mIpServer = new IpServer(
-                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
+                IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
                 mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator,
                 mTetheringMetrics, mDependencies);
         mIpServer.start();
@@ -324,7 +325,7 @@
         mBpfDeps = new BpfCoordinator.Dependencies() {
                     @NonNull
                     public Handler getHandler() {
-                        return new Handler(mLooper.getLooper());
+                        return mHandler;
                     }
 
                     @NonNull
@@ -402,7 +403,7 @@
     public void startsOutAvailable() {
         when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
                 .thenReturn(mIpNeighborMonitor);
-        mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
+        mIpServer = new IpServer(IFACE_NAME, mHandler, TETHERING_BLUETOOTH, mSharedLog,
                 mNetd, mBpfCoordinator, mRoutingCoordinatorManager, mCallback, mTetherConfig,
                 mAddressCoordinator, mTetheringMetrics, mDependencies);
         mIpServer.start();
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
new file mode 100644
index 0000000..2c4df76
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkstack.tethering.util
+
+import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.util.State
+import com.android.networkstack.tethering.util.StateMachineShim.AsyncStateMachine
+import com.android.networkstack.tethering.util.StateMachineShim.Dependencies
+import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class StateMachineShimTest {
+    private val mSyncSM = mock(SyncStateMachine::class.java)
+    private val mAsyncSM = mock(AsyncStateMachine::class.java)
+    private val mState1 = mock(State::class.java)
+    private val mState2 = mock(State::class.java)
+
+    inner class MyDependencies() : Dependencies() {
+
+        override fun makeSyncStateMachine(name: String, thread: Thread) = mSyncSM
+
+        override fun makeAsyncStateMachine(name: String, looper: Looper) = mAsyncSM
+    }
+
+    @Test
+    fun testUsingSyncStateMachine() {
+        val inOrder = inOrder(mSyncSM, mAsyncSM)
+        val shimUsingSyncSM = StateMachineShim("ShimTest", null, MyDependencies())
+        shimUsingSyncSM.start(mState1)
+        inOrder.verify(mSyncSM).start(mState1)
+
+        val allStates = ArrayList<StateInfo>()
+        allStates.add(StateInfo(mState1, null))
+        allStates.add(StateInfo(mState2, mState1))
+        shimUsingSyncSM.addAllStates(allStates)
+        inOrder.verify(mSyncSM).addAllStates(allStates)
+
+        shimUsingSyncSM.transitionTo(mState1)
+        inOrder.verify(mSyncSM).transitionTo(mState1)
+
+        val what = 10
+        shimUsingSyncSM.sendMessage(what)
+        inOrder.verify(mSyncSM).processMessage(what, 0, 0, null)
+        val obj = Object()
+        shimUsingSyncSM.sendMessage(what, obj)
+        inOrder.verify(mSyncSM).processMessage(what, 0, 0, obj)
+        val arg1 = 11
+        shimUsingSyncSM.sendMessage(what, arg1)
+        inOrder.verify(mSyncSM).processMessage(what, arg1, 0, null)
+        val arg2 = 12
+        shimUsingSyncSM.sendMessage(what, arg1, arg2, obj)
+        inOrder.verify(mSyncSM).processMessage(what, arg1, arg2, obj)
+
+        assertFailsWith(IllegalStateException::class) {
+            shimUsingSyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
+        }
+
+        shimUsingSyncSM.sendSelfMessageToSyncSM(what, obj)
+        inOrder.verify(mSyncSM).sendSelfMessage(what, 0, 0, obj)
+
+        verifyNoMoreInteractions(mSyncSM, mAsyncSM)
+    }
+
+    @Test
+    fun testUsingAsyncStateMachine() {
+        val inOrder = inOrder(mSyncSM, mAsyncSM)
+        val shimUsingAsyncSM = StateMachineShim("ShimTest", mock(Looper::class.java),
+                MyDependencies())
+        shimUsingAsyncSM.start(mState1)
+        inOrder.verify(mAsyncSM).setInitialState(mState1)
+        inOrder.verify(mAsyncSM).start()
+
+        val allStates = ArrayList<StateInfo>()
+        allStates.add(StateInfo(mState1, null))
+        allStates.add(StateInfo(mState2, mState1))
+        shimUsingAsyncSM.addAllStates(allStates)
+        inOrder.verify(mAsyncSM).addState(mState1, null)
+        inOrder.verify(mAsyncSM).addState(mState2, mState1)
+
+        shimUsingAsyncSM.transitionTo(mState1)
+        inOrder.verify(mAsyncSM).transitionTo(mState1)
+
+        val what = 10
+        shimUsingAsyncSM.sendMessage(what)
+        inOrder.verify(mAsyncSM).sendMessage(what, 0, 0, null)
+        val obj = Object()
+        shimUsingAsyncSM.sendMessage(what, obj)
+        inOrder.verify(mAsyncSM).sendMessage(what, 0, 0, obj)
+        val arg1 = 11
+        shimUsingAsyncSM.sendMessage(what, arg1)
+        inOrder.verify(mAsyncSM).sendMessage(what, arg1, 0, null)
+        val arg2 = 12
+        shimUsingAsyncSM.sendMessage(what, arg1, arg2, obj)
+        inOrder.verify(mAsyncSM).sendMessage(what, arg1, arg2, obj)
+
+        shimUsingAsyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
+        inOrder.verify(mAsyncSM).sendMessageDelayed(what, 1000)
+
+        assertFailsWith(IllegalStateException::class) {
+            shimUsingAsyncSM.sendSelfMessageToSyncSM(what, obj)
+        }
+
+        verifyNoMoreInteractions(mSyncSM, mAsyncSM)
+    }
+}