Merge "Adopt prev preferred APN on APN reset"
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.