Merge "Use WakeupMesage for delayed events in VcnGatewayConnection."

This commit is contained in:
Cody Kesting
2021-02-16 14:54:30 +00:00
committed by Gerrit Code Review
8 changed files with 280 additions and 39 deletions

View File

@@ -61,6 +61,7 @@ import android.os.Message;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Slog; import android.util.Slog;
@@ -68,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.State; import com.android.internal.util.State;
import com.android.internal.util.StateMachine; import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
@@ -129,8 +131,8 @@ import java.util.concurrent.TimeUnit;
* lack of WakeLocks). * lack of WakeLocks).
* *
* <p>Any attempt to remove messages from the Handler should be done using {@link * <p>Any attempt to remove messages from the Handler should be done using {@link
* #removeEqualMessages(int, Object)}. This is necessary to ensure that the WakeLock is correctly * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when
* released when no messages remain in the Handler queue. * no messages remain in the Handler queue.
* *
* @hide * @hide
*/ */
@@ -140,6 +142,15 @@ public class VcnGatewayConnection extends StateMachine {
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM";
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM";
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
private static final int[] MERGED_CAPABILITIES = private static final int[] MERGED_CAPABILITIES =
new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
@@ -150,7 +161,8 @@ public class VcnGatewayConnection extends StateMachine {
private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
private static final int TOKEN_ALL = Integer.MIN_VALUE; private static final int TOKEN_ALL = Integer.MIN_VALUE;
private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; @VisibleForTesting(visibility = Visibility.PRIVATE)
static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
static final int TEARDOWN_TIMEOUT_SECONDS = 5; static final int TEARDOWN_TIMEOUT_SECONDS = 5;
@@ -510,6 +522,10 @@ public class VcnGatewayConnection extends StateMachine {
*/ */
private NetworkAgent mNetworkAgent; private NetworkAgent mNetworkAgent;
@Nullable private WakeupMessage mTeardownTimeoutAlarm;
@Nullable private WakeupMessage mDisconnectRequestAlarm;
@Nullable private WakeupMessage mRetryTimeoutAlarm;
public VcnGatewayConnection( public VcnGatewayConnection(
@NonNull VcnContext vcnContext, @NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup, @NonNull ParcelUuid subscriptionGroup,
@@ -592,6 +608,10 @@ public class VcnGatewayConnection extends StateMachine {
releaseWakeLock(); releaseWakeLock();
cancelTeardownTimeoutAlarm();
cancelDisconnectRequestAlarm();
cancelRetryTimeoutAlarm();
mUnderlyingNetworkTracker.teardown(); mUnderlyingNetworkTracker.teardown();
} }
@@ -623,17 +643,11 @@ public class VcnGatewayConnection extends StateMachine {
// If underlying is null, all underlying networks have been lost. Disconnect VCN after a // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
// timeout. // timeout.
if (underlying == null) { if (underlying == null) {
sendMessageDelayed( setDisconnectRequestAlarm();
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST),
TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
} else { } else {
// Cancel any existing disconnect due to previous loss of underlying network // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
removeEqualMessages( // and cancel any queued EVENT_DISCONNECT_REQUEST messages
EVENT_DISCONNECT_REQUESTED, cancelDisconnectRequestAlarm();
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
} }
sendMessageAndAcquireWakeLock( sendMessageAndAcquireWakeLock(
@@ -724,6 +738,10 @@ public class VcnGatewayConnection extends StateMachine {
super.sendMessage(msg); super.sendMessage(msg);
} }
// TODO(b/180146061): also override and Log.wtf() other Message handling methods
// In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
// removeDeferredMessages
/** /**
* WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
* go to sleep before processing the sent message. * go to sleep before processing the sent message.
@@ -751,14 +769,13 @@ public class VcnGatewayConnection extends StateMachine {
super.sendMessage(what, token, arg2, data); super.sendMessage(what, token, arg2, data);
} }
// TODO: remove this method once WakupMessage is used instead /**
private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) { * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout); * go to sleep before processing the sent message.
} */
private void sendMessageAndAcquireWakeLock(Message msg) {
// TODO: remove this method once WakupMessage is used instead acquireWakeLock();
private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) { super.sendMessage(msg);
super.sendMessageDelayed(what, token, arg2, data, timeout);
} }
/** /**
@@ -788,6 +805,100 @@ public class VcnGatewayConnection extends StateMachine {
maybeReleaseWakeLock(); maybeReleaseWakeLock();
} }
private WakeupMessage createScheduledAlarm(
@NonNull String cmdName, Message delayedMessage, long delay) {
// WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
// at the scheduled time. dispatchMessage() immediately executes and there may be queued
// events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
// place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
// guarantees the device will stay awake).
final WakeupMessage alarm =
mDeps.newWakeupMessage(
mVcnContext,
getHandler(),
cmdName,
() -> sendMessageAndAcquireWakeLock(delayedMessage));
alarm.schedule(mDeps.getElapsedRealTime() + delay);
return alarm;
}
private void setTeardownTimeoutAlarm() {
// Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
// either case, there is nothing to cancel.
if (mTeardownTimeoutAlarm != null) {
Slog.wtf(TAG, "mTeardownTimeoutAlarm should be null before being set");
}
final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
mTeardownTimeoutAlarm =
createScheduledAlarm(
TEARDOWN_TIMEOUT_ALARM,
delayedMessage,
TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
}
private void cancelTeardownTimeoutAlarm() {
if (mTeardownTimeoutAlarm != null) {
mTeardownTimeoutAlarm.cancel();
mTeardownTimeoutAlarm = null;
}
// Cancel any existing teardown timeouts
removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
}
private void setDisconnectRequestAlarm() {
// Only schedule a NEW alarm if none is already set.
if (mDisconnectRequestAlarm != null) {
return;
}
final Message delayedMessage =
obtainMessage(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
0 /* arg2 */,
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
mDisconnectRequestAlarm =
createScheduledAlarm(
DISCONNECT_REQUEST_ALARM,
delayedMessage,
TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
}
private void cancelDisconnectRequestAlarm() {
if (mDisconnectRequestAlarm != null) {
mDisconnectRequestAlarm.cancel();
mDisconnectRequestAlarm = null;
}
// Cancel any existing disconnect due to previous loss of underlying network
removeEqualMessages(
EVENT_DISCONNECT_REQUESTED,
new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
}
private void setRetryTimeoutAlarm(long delay) {
// Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
// either case, there is nothing to cancel.
if (mRetryTimeoutAlarm != null) {
Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set");
}
final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
}
private void cancelRetryTimeoutAlarm() {
if (mRetryTimeoutAlarm != null) {
mRetryTimeoutAlarm.cancel();
mRetryTimeoutAlarm = null;
}
removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
}
private void sessionLost(int token, @Nullable Exception exception) { private void sessionLost(int token, @Nullable Exception exception) {
sendMessageAndAcquireWakeLock( sendMessageAndAcquireWakeLock(
EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
@@ -847,8 +958,8 @@ public class VcnGatewayConnection extends StateMachine {
* builds. * builds.
* *
* <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
* the Handler queue is empty. Future changes to processMessage() (or overrides to * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
* processMessage) MUST ensure that mWakeLock is correctly released. * ensure that mWakeLock is correctly released.
*/ */
@Override @Override
public final boolean processMessage(Message msg) { public final boolean processMessage(Message msg) {
@@ -1030,10 +1141,9 @@ public class VcnGatewayConnection extends StateMachine {
} }
mIkeSession.close(); mIkeSession.close();
sendMessageDelayed(
EVENT_TEARDOWN_TIMEOUT_EXPIRED, // Safe to blindly set up, as it is cancelled and cleared on exiting this state
mCurrentToken, setTeardownTimeoutAlarm();
TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
} }
@Override @Override
@@ -1084,6 +1194,8 @@ public class VcnGatewayConnection extends StateMachine {
@Override @Override
protected void exitState() throws Exception { protected void exitState() throws Exception {
mSkipRetryTimeout = false; mSkipRetryTimeout = false;
cancelTeardownTimeoutAlarm();
} }
} }
@@ -1385,8 +1497,8 @@ public class VcnGatewayConnection extends StateMachine {
Slog.wtf(TAG, "Underlying network was null in retry state"); Slog.wtf(TAG, "Underlying network was null in retry state");
transitionTo(mDisconnectedState); transitionTo(mDisconnectedState);
} else { } else {
sendMessageDelayed( // Safe to blindly set up, as it is cancelled and cleared on exiting this state
EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs()); setRetryTimeoutAlarm(getNextRetryIntervalsMs());
} }
} }
@@ -1399,8 +1511,6 @@ public class VcnGatewayConnection extends StateMachine {
// If new underlying is null, all networks were lost; go back to disconnected. // If new underlying is null, all networks were lost; go back to disconnected.
if (mUnderlying == null) { if (mUnderlying == null) {
removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
transitionTo(mDisconnectedState); transitionTo(mDisconnectedState);
return; return;
} else if (oldUnderlying != null } else if (oldUnderlying != null
@@ -1411,8 +1521,6 @@ public class VcnGatewayConnection extends StateMachine {
// Fallthrough // Fallthrough
case EVENT_RETRY_TIMEOUT_EXPIRED: case EVENT_RETRY_TIMEOUT_EXPIRED:
removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
transitionTo(mConnectingState); transitionTo(mConnectingState);
break; break;
case EVENT_DISCONNECT_REQUESTED: case EVENT_DISCONNECT_REQUESTED:
@@ -1424,6 +1532,11 @@ public class VcnGatewayConnection extends StateMachine {
} }
} }
@Override
public void exitState() {
cancelRetryTimeoutAlarm();
}
private long getNextRetryIntervalsMs() { private long getNextRetryIntervalsMs() {
final int retryDelayIndex = mFailedAttempts - 1; final int retryDelayIndex = mFailedAttempts - 1;
final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs(); final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs();
@@ -1702,6 +1815,20 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) { @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) {
return new VcnWakeLock(context, wakeLockFlag, wakeLockTag); return new VcnWakeLock(context, wakeLockFlag, wakeLockTag);
} }
/** Builds a new WakeupMessage. */
public WakeupMessage newWakeupMessage(
@NonNull VcnContext vcnContext,
@NonNull Handler handler,
@NonNull String tag,
@NonNull Runnable runnable) {
return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
}
/** Gets the elapsed real time since boot, in millis. */
public long getElapsedRealTime() {
return SystemClock.elapsedRealtime();
}
} }
/** /**

View File

@@ -81,6 +81,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
verify(mIkeSession, never()).close(); verify(mIkeSession, never()).close();
verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
} }
@Test @Test
@@ -170,6 +171,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
} }
@Test @Test
@@ -179,5 +181,6 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close(); verify(mIkeSession).close();
verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
} }
} }

View File

@@ -61,6 +61,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verify(mIkeSession).kill(); verify(mIkeSession).kill();
verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
} }
@Test @Test
@@ -73,6 +74,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close(); verify(mIkeSession).close();
verify(mIkeSession, never()).kill(); verify(mIkeSession, never()).kill();
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
} }
@Test @Test
@@ -92,6 +94,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close(); verify(mIkeSession).close();
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
} }
@Test @Test
@@ -101,5 +104,6 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close(); verify(mIkeSession).close();
verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
} }
} }

View File

@@ -88,6 +88,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
} }
@Test @Test

View File

@@ -16,9 +16,9 @@
package com.android.server.vcn; package com.android.server.vcn;
import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -28,8 +28,6 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.util.concurrent.TimeUnit;
/** Tests for VcnGatewayConnection.DisconnectedState */ /** Tests for VcnGatewayConnection.DisconnectedState */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
@@ -40,6 +38,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession()); mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
// ensure that mGatewayConnection has an underlying Network before entering
// DisconnectingState
mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_2);
mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState); mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
} }
@@ -49,12 +50,22 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
getIkeSessionCallback().onClosed(); getIkeSessionCallback().onClosed();
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mMockIkeSession).close();
verify(mMockIkeSession, never()).kill();
verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
} }
@Test @Test
public void testTimeoutExpired() throws Exception { public void testTimeoutExpired() throws Exception {
mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); Runnable delayedEvent =
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
// Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
// (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
// since verifyTeardownTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
// with the correct delay.
delayedEvent.run();
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
verify(mMockIkeSession).kill(); verify(mMockIkeSession).kill();
@@ -67,5 +78,6 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
// Should do nothing; already tearing down. // Should do nothing; already tearing down.
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
} }
} }

View File

@@ -29,10 +29,14 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase { public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase {
private long mFirstRetryInterval;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
mFirstRetryInterval = mConfig.getRetryInterval()[0];
mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState); mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState);
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
@@ -46,6 +50,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
} }
@Test @Test
@@ -56,6 +61,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */);
} }
@Test @Test
@@ -66,13 +72,23 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
} }
@Test @Test
public void testTimeoutElapsingTriggersRetry() throws Exception { public void testTimeoutElapsingTriggersRetry() throws Exception {
mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]); final Runnable delayedEvent =
verifyRetryTimeoutAlarmAndGetCallback(
mFirstRetryInterval, false /* expectCanceled */);
// Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
// (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
// since verifyRetryTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
// with the correct delay.
delayedEvent.run();
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
} }
} }

View File

@@ -145,4 +145,19 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
verifyWakeLockReleased(); verifyWakeLockReleased();
} }
@Test
public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() {
mGatewayConnection
.getUnderlyingNetworkTrackerCallback()
.onSelectedUnderlyingNetworkChanged(null);
verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
mGatewayConnection
.getUnderlyingNetworkTrackerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
verify(mDisconnectRequestAlarm).cancel();
}
} }

View File

@@ -22,8 +22,11 @@ import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -47,6 +50,7 @@ import android.os.ParcelUuid;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.test.TestLooper; import android.os.test.TestLooper;
import com.android.internal.util.WakeupMessage;
import com.android.server.IpSecService; import com.android.server.IpSecService;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
@@ -59,6 +63,7 @@ import org.mockito.ArgumentCaptor;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Collections; import java.util.Collections;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class VcnGatewayConnectionTestBase { public class VcnGatewayConnectionTestBase {
protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID()); protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
@@ -72,6 +77,7 @@ public class VcnGatewayConnectionTestBase {
protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2; protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3; protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
protected static final int TEST_SUB_ID = 5; protected static final int TEST_SUB_ID = 5;
protected static final long ELAPSED_REAL_TIME = 123456789L;
protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
new UnderlyingNetworkRecord( new UnderlyingNetworkRecord(
@@ -99,6 +105,9 @@ public class VcnGatewayConnectionTestBase {
@NonNull protected final VcnGatewayConnection.Dependencies mDeps; @NonNull protected final VcnGatewayConnection.Dependencies mDeps;
@NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
@NonNull protected final VcnWakeLock mWakeLock; @NonNull protected final VcnWakeLock mWakeLock;
@NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
@NonNull protected final WakeupMessage mDisconnectRequestAlarm;
@NonNull protected final WakeupMessage mRetryTimeoutAlarm;
@NonNull protected final IpSecService mIpSecSvc; @NonNull protected final IpSecService mIpSecSvc;
@NonNull protected final ConnectivityManager mConnMgr; @NonNull protected final ConnectivityManager mConnMgr;
@@ -116,6 +125,9 @@ public class VcnGatewayConnectionTestBase {
mDeps = mock(VcnGatewayConnection.Dependencies.class); mDeps = mock(VcnGatewayConnection.Dependencies.class);
mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
mWakeLock = mock(VcnWakeLock.class); mWakeLock = mock(VcnWakeLock.class);
mTeardownTimeoutAlarm = mock(WakeupMessage.class);
mDisconnectRequestAlarm = mock(WakeupMessage.class);
mRetryTimeoutAlarm = mock(WakeupMessage.class);
mIpSecSvc = mock(IpSecService.class); mIpSecSvc = mock(IpSecService.class);
setupIpSecManager(mContext, mIpSecSvc); setupIpSecManager(mContext, mIpSecSvc);
@@ -134,6 +146,16 @@ public class VcnGatewayConnectionTestBase {
doReturn(mWakeLock) doReturn(mWakeLock)
.when(mDeps) .when(mDeps)
.newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
}
private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) {
doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
} }
@Before @Before
@@ -190,4 +212,45 @@ public class VcnGatewayConnectionTestBase {
verify(mWakeLock).release(); verify(mWakeLock).release();
verifyNoMoreInteractions(mWakeLock); verifyNoMoreInteractions(mWakeLock);
} }
private Runnable verifyWakeupMessageSetUpAndGetCallback(
@NonNull String tag,
@NonNull WakeupMessage msg,
long delayInMillis,
boolean expectCanceled) {
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(tag), runnableCaptor.capture());
verify(mDeps, atLeastOnce()).getElapsedRealTime();
verify(msg).schedule(ELAPSED_REAL_TIME + delayInMillis);
verify(msg, expectCanceled ? times(1) : never()).cancel();
return runnableCaptor.getValue();
}
protected Runnable verifyTeardownTimeoutAlarmAndGetCallback(boolean expectCanceled) {
return verifyWakeupMessageSetUpAndGetCallback(
VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM,
mTeardownTimeoutAlarm,
TimeUnit.SECONDS.toMillis(VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS),
expectCanceled);
}
protected Runnable verifyDisconnectRequestAlarmAndGetCallback(boolean expectCanceled) {
return verifyWakeupMessageSetUpAndGetCallback(
VcnGatewayConnection.DISCONNECT_REQUEST_ALARM,
mDisconnectRequestAlarm,
TimeUnit.SECONDS.toMillis(
VcnGatewayConnection.NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS),
expectCanceled);
}
protected Runnable verifyRetryTimeoutAlarmAndGetCallback(
long delayInMillis, boolean expectCanceled) {
return verifyWakeupMessageSetUpAndGetCallback(
VcnGatewayConnection.RETRY_TIMEOUT_ALARM,
mRetryTimeoutAlarm,
delayInMillis,
expectCanceled);
}
} }