SyncSM06: Add StateMachineShim

Test: atest StateMachineShimTest

Change-Id: Ic818aa55e7e0fd7a62dfce50a6ad719e6e1c44ec
This commit is contained in:
Mark 2022-11-16 11:27:06 +00:00
commit 2ec1595d0b
3 changed files with 307 additions and 0 deletions

View file

@ -136,6 +136,9 @@ public class TetheringConfiguration {
*/
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;

View file

@ -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);
}
}
}

View file

@ -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)
}
}