diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 4d5cf4ff750ac..2503e812f9e1b 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -1208,7 +1208,64 @@ public class VcnGatewayConnection extends StateMachine { */ class RetryTimeoutState extends ActiveBaseState { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() throws Exception { + // Reset upon entry to ConnectedState + mFailedAttempts++; + + if (mUnderlying == null) { + Slog.wtf(TAG, "Underlying network was null in retry state"); + transitionTo(mDisconnectedState); + } else { + sendMessageDelayed( + EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs()); + } + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: + final UnderlyingNetworkRecord oldUnderlying = mUnderlying; + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + // If new underlying is null, all networks were lost; go back to disconnected. + if (mUnderlying == null) { + removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); + + transitionTo(mDisconnectedState); + return; + } else if (oldUnderlying != null + && mUnderlying.network.equals(oldUnderlying.network)) { + // If the network has not changed, do nothing. + return; + } + + // Fallthrough + case EVENT_RETRY_TIMEOUT_EXPIRED: + removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); + + transitionTo(mConnectingState); + break; + case EVENT_DISCONNECT_REQUESTED: + handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + break; + default: + logUnhandledMessage(msg); + break; + } + } + + private long getNextRetryIntervalsMs() { + final int retryDelayIndex = mFailedAttempts - 1; + final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs(); + + // Repeatedly use last item in retry timeout list. + if (retryDelayIndex >= retryIntervalsMs.length) { + return retryIntervalsMs[retryIntervalsMs.length - 1]; + } + + return retryIntervalsMs[retryDelayIndex]; + } } @VisibleForTesting(visibility = Visibility.PRIVATE) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java new file mode 100644 index 0000000000000..3f2b47cd58fd5 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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.server.vcn; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for VcnGatewayConnection.RetryTimeoutState */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase { + @Before + public void setUp() throws Exception { + super.setUp(); + + mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); + mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState); + mTestLooper.dispatchAll(); + } + + @Test + public void testNewNetworkTriggerRetry() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testSameNetworkDoesNotTriggerRetry() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testNullNetworkTriggersDisconnect() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testTimeoutElapsingTriggersRetry() throws Exception { + mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + } +}