Merge "Cancel pending retry upon data profile unthrottle"
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index ec5ba1b..e8e694d 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -969,18 +969,16 @@
switch (msg.what) {
case EVENT_DATA_SETUP_RETRY:
DataSetupRetryEntry dataSetupRetryEntry = (DataSetupRetryEntry) msg.obj;
- Objects.requireNonNull(dataSetupRetryEntry);
- mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
- () -> callback.onDataNetworkSetupRetry(dataSetupRetryEntry)));
+ if (!isRetryCancelled(dataSetupRetryEntry)) {
+ mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+ () -> callback.onDataNetworkSetupRetry(dataSetupRetryEntry)));
+ }
break;
case EVENT_DATA_HANDOVER_RETRY:
DataHandoverRetryEntry dataHandoverRetryEntry = (DataHandoverRetryEntry) msg.obj;
- Objects.requireNonNull(dataHandoverRetryEntry);
- if (mDataRetryEntries.contains(dataHandoverRetryEntry)) {
+ if (!isRetryCancelled(dataHandoverRetryEntry)) {
mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onDataNetworkHandoverRetry(dataHandoverRetryEntry)));
- } else {
- log("Handover was cancelled earlier. " + dataHandoverRetryEntry);
}
break;
case EVENT_RADIO_ON:
@@ -1014,6 +1012,18 @@
}
/**
+ * @param retryEntry The retry entry to check.
+ * @return {@code true} if the retry is null or not in RETRY_STATE_NOT_RETRIED state.
+ */
+ private boolean isRetryCancelled(@Nullable DataRetryEntry retryEntry) {
+ if (retryEntry != null && retryEntry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED) {
+ return false;
+ }
+ log("Retry was removed earlier. " + retryEntry);
+ return true;
+ }
+
+ /**
* Called when carrier config is updated.
*/
private void onCarrierConfigUpdated() {
@@ -1385,7 +1395,7 @@
* @param remove Whether to remove unthrottled entries from the list of entries.
*/
private void onDataProfileUnthrottled(@Nullable DataProfile dataProfile, @Nullable String apn,
- int transport, boolean remove) {
+ @TransportType int transport, boolean remove) {
log("onDataProfileUnthrottled: data profile=" + dataProfile + ", apn=" + apn
+ ", transport=" + AccessNetworkConstants.transportTypeToString(transport)
+ ", remove=" + remove);
@@ -1456,11 +1466,16 @@
mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onThrottleStatusChanged(throttleStatusList)));
+ if (unthrottledProfile != null) {
+ // cancel pending retries since we will soon schedule an immediate retry
+ cancelRetriesForDataProfile(unthrottledProfile, transport);
+ }
+
logl("onDataProfileUnthrottled: Removing the following throttling entries. "
+ dataUnthrottlingEntries);
for (DataThrottlingEntry entry : dataUnthrottlingEntries) {
+ // Immediately retry after unthrottling.
if (entry.retryType == ThrottleStatus.RETRY_TYPE_NEW_CONNECTION) {
- // Immediately retry after unthrottling.
schedule(new DataSetupRetryEntry.Builder<>()
.setDataProfile(entry.dataProfile)
.setTransport(entry.transport)
@@ -1481,6 +1496,34 @@
}
/**
+ * Cancel pending retries that uses the specified data profile, with specified target transport.
+ *
+ * @param dataProfile The data profile to cancel.
+ * @param transport The target {@link TransportType} on which the retry to cancel.
+ */
+ private void cancelRetriesForDataProfile(@NonNull DataProfile dataProfile,
+ @TransportType int transport) {
+ logl("cancelRetriesForDataProfile: Canceling pending retries for " + dataProfile);
+ mDataRetryEntries.stream()
+ .filter(entry -> {
+ if (entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED) {
+ if (entry instanceof DataSetupRetryEntry) {
+ DataSetupRetryEntry retryEntry = (DataSetupRetryEntry) entry;
+ return dataProfile.equals(retryEntry.dataProfile)
+ && transport == retryEntry.transport;
+ } else if (entry instanceof DataHandoverRetryEntry) {
+ DataHandoverRetryEntry retryEntry = (DataHandoverRetryEntry) entry;
+ return dataProfile.equals(retryEntry.dataNetwork.getDataProfile());
+ }
+ }
+ return false;
+ })
+ .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
+ }
+
+
+
+ /**
* Check if there is any similar network request scheduled to retry. The definition of similar
* is that network requests have same APN capability and on the same transport.
*
@@ -1564,14 +1607,18 @@
* @param dataNetwork The data network that was originally scheduled for handover retry.
*/
private void onCancelPendingHandoverRetry(@NonNull DataNetwork dataNetwork) {
- mDataRetryEntries.removeIf(entry -> entry instanceof DataHandoverRetryEntry
- && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork);
+ mDataRetryEntries.stream()
+ .filter(entry -> entry instanceof DataHandoverRetryEntry
+ && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork
+ && entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED)
+ .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
mDataThrottlingEntries.removeIf(entry -> entry.dataNetwork == dataNetwork);
}
/**
* Check if there is any data handover retry scheduled.
*
+ *
* @param dataNetwork The network network to retry handover.
* @return {@code true} if there is retry scheduled for this network capability.
*/
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
index 4fbc31c..15eb0ea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony.data;
+import static com.android.internal.telephony.data.DataRetryManager.DataHandoverRetryEntry;
+import static com.android.internal.telephony.data.DataRetryManager.DataRetryEntry;
import static com.android.internal.telephony.data.DataRetryManager.DataSetupRetryEntry;
import static com.google.common.truth.Truth.assertThat;
@@ -25,6 +27,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.NetworkCapabilities;
@@ -54,6 +57,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
+import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
@@ -323,15 +327,38 @@
}
@Test
- public void testDataSetupUnthrottling() {
+ public void testDataSetupUnthrottling() throws Exception {
testDataSetupRetryNetworkSuggestedNeverRetry();
Mockito.clearInvocations(mDataRetryManagerCallbackMock);
+ DataNetworkController.NetworkRequestList mockNrl = Mockito.mock(
+ DataNetworkController.NetworkRequestList.class);
+ Field field = DataRetryManager.class.getDeclaredField("mDataRetryEntries");
+ field.setAccessible(true);
+ List<DataRetryEntry> mDataRetryEntries =
+ (List<DataRetryEntry>) field.get(mDataRetryManagerUT);
+ // schedule 2 setup retries
+ DataSetupRetryEntry scheduledRetry1 = new DataSetupRetryEntry.Builder<>()
+ .setDataProfile(mDataProfile3)
+ .setNetworkRequestList(mockNrl)
+ .setTransport(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setSetupRetryType(1)
+ .build();
+ DataSetupRetryEntry scheduledRetry2 = new DataSetupRetryEntry.Builder<>()
+ .setNetworkRequestList(mockNrl)
+ .setDataProfile(mDataProfile3)
+ .setTransport(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+ .setSetupRetryType(1)
+ .build();
+ mDataRetryEntries.addAll(List.of(scheduledRetry1, scheduledRetry2));
+
+ // unthrottle the data profile, expect previous retries of the same transport is cancelled
mDataRetryManagerUT.obtainMessage(6/*EVENT_DATA_PROFILE_UNTHROTTLED*/,
new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, mDataProfile3, null))
.sendToTarget();
processAllMessages();
+ // check unthrottle
ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
ArgumentCaptor.forClass(List.class);
verify(mDataRetryManagerCallbackMock).onThrottleStatusChanged(
@@ -353,6 +380,10 @@
assertThat(entry.dataProfile).isEqualTo(mDataProfile3);
assertThat(entry.retryDelayMillis).isEqualTo(0);
assertThat(entry.transport).isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ // check mDataProfile3-WWAN retry is cancelled, but not the WLAN
+ assertThat(scheduledRetry1.getState()).isEqualTo(DataRetryEntry.RETRY_STATE_CANCELLED);
+ assertThat(scheduledRetry2.getState()).isEqualTo(DataRetryEntry.RETRY_STATE_NOT_RETRIED);
}
@Test
@@ -391,6 +422,66 @@
}
@Test
+ public void testCancellingRetries() throws Exception {
+ DataNetworkController.NetworkRequestList mockNrl = Mockito.mock(
+ DataNetworkController.NetworkRequestList.class);
+
+ // Test: setup retry
+ DataRetryEntry retry = new DataSetupRetryEntry.Builder<>()
+ .setSetupRetryType(1)
+ .setNetworkRequestList(mockNrl)
+ .setTransport(1)
+ .build();
+ retry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
+
+ mDataRetryManagerUT.obtainMessage(3/*EVENT_DATA_SETUP_RETRY*/, retry).sendToTarget();
+ processAllMessages();
+ verify(mDataRetryManagerCallbackMock, never()).onDataNetworkSetupRetry(any());
+
+ mDataRetryManagerUT.obtainMessage(3/*EVENT_DATA_SETUP_RETRY*/, null).sendToTarget();
+ processAllMessages();
+ verify(mDataRetryManagerCallbackMock, never()).onDataNetworkSetupRetry(any());
+
+ retry.setState(DataRetryEntry.RETRY_STATE_NOT_RETRIED);
+ mDataRetryManagerUT.obtainMessage(3/*EVENT_DATA_SETUP_RETRY*/, retry).sendToTarget();
+ processAllMessages();
+ verify(mDataRetryManagerCallbackMock, times(1)).onDataNetworkSetupRetry(any());
+
+ // Test: handover retry
+ retry = new DataHandoverRetryEntry.Builder<>().build();
+ retry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
+ mDataRetryManagerUT.obtainMessage(4/*EVENT_DATA_HANDOVER_RETRY*/, retry).sendToTarget();
+ processAllMessages();
+ verify(mDataRetryManagerCallbackMock, never()).onDataNetworkHandoverRetry(any());
+
+ mDataRetryManagerUT.obtainMessage(4/*EVENT_DATA_HANDOVER_RETRY*/, null).sendToTarget();
+ processAllMessages();
+ verify(mDataRetryManagerCallbackMock, never()).onDataNetworkHandoverRetry(any());
+
+ retry.setState(DataRetryEntry.RETRY_STATE_NOT_RETRIED);
+ mDataRetryManagerUT.obtainMessage(4/*EVENT_DATA_HANDOVER_RETRY*/, retry).sendToTarget();
+ processAllMessages();
+ verify(mDataRetryManagerCallbackMock, times(1))
+ .onDataNetworkHandoverRetry(any());
+
+ // Test: cancelPendingHandoverRetry
+ DataNetwork mockDn = Mockito.mock(DataNetwork.class);
+ Field field = DataRetryManager.class.getDeclaredField("mDataRetryEntries");
+ field.setAccessible(true);
+ List<DataRetryEntry> mDataRetryEntries =
+ (List<DataRetryEntry>) field.get(mDataRetryManagerUT);
+ retry = new DataHandoverRetryEntry.Builder<>()
+ .setDataNetwork(mockDn)
+ .build();
+ mDataRetryEntries.add(retry);
+ mDataRetryManagerUT.cancelPendingHandoverRetry(mockDn);
+ processAllMessages();
+
+ assertThat(mDataRetryManagerUT.isAnyHandoverRetryScheduled(mockDn)).isFalse();
+ assertThat(retry.getState()).isEqualTo(DataRetryEntry.RETRY_STATE_CANCELLED);
+ }
+
+ @Test
public void testDataSetupRetryPermanentFailure() {
DataSetupRetryRule retryRule = new DataSetupRetryRule(
"fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
@@ -584,7 +675,7 @@
assertThat(entry.networkRequestList).isEqualTo(networkRequestList);
assertThat(entry.appliedDataRetryRule).isEqualTo(retryRule3);
- entry.setState(DataRetryManager.DataRetryEntry.RETRY_STATE_FAILED);
+ entry.setState(DataRetryEntry.RETRY_STATE_FAILED);
}
// The last fail should not trigger any retry.