Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39ec9797 authored by Ling Ma's avatar Ling Ma
Browse files

Use AlarmManager to schedule long retry

The limitation with handler message is that they extremely delayed when
device is in (light) doze mode, even when it enters the maintenance window. The experiment suggests that delay occurrs for timer longer than 1 minute, therefore this change uses AlarmManager to schedule longer retries.

Fix: 271037454
Test: Reproduced the issue and confirmed the fix by experiment
Change-Id: I676ecd9e593d7e774993cb941ecf1791034fa148
parent 04ed236f
Loading
Loading
Loading
Loading
+76 −9
Original line number Diff line number Diff line
@@ -21,6 +21,12 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkCapabilities;
import android.os.AsyncResult;
import android.os.Handler;
@@ -72,6 +78,11 @@ import java.util.stream.Stream;
public class DataRetryManager extends Handler {
    private static final boolean VDBG = false;

    /** Intent of Alarm Manager for long retry timer. */
    private static final String ACTION_RETRY = "com.android.internal.telephony.data.ACTION_RETRY";
    /** The extra key for the hashcode of the retry entry for Alarm Manager. */
    private static final String ACTION_RETRY_EXTRA_HASHCODE = "extra_retry_hashcode";

    /** Event for data setup retry. */
    private static final int EVENT_DATA_SETUP_RETRY = 3;

@@ -98,6 +109,12 @@ public class DataRetryManager extends Handler {

    /** The maximum entries to preserve. */
    private static final int MAXIMUM_HISTORICAL_ENTRIES = 100;
    /**
     * The threshold of retry timer, longer than or equal to which we use alarm manager to schedule
     * instead of handler.
     */
    private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit
            .MINUTES.toMillis(1);

    @IntDef(prefix = {"RESET_REASON_"},
            value = {
@@ -143,6 +160,9 @@ public class DataRetryManager extends Handler {
    /** Local log. */
    private final @NonNull LocalLog mLocalLog = new LocalLog(128);

    /** Alarm Manager used to schedule long set up or handover retries. */
    private final @NonNull AlarmManager mAlarmManager;

    /**
     * The data retry callback. This is only used to notify {@link DataNetworkController} to retry
     * setup data network.
@@ -952,6 +972,8 @@ public class DataRetryManager extends Handler {
        mDataServiceManagers = dataServiceManagers;
        mDataConfigManager = dataNetworkController.getDataConfigManager();
        mDataProfileManager = dataNetworkController.getDataProfileManager();
        mAlarmManager = mPhone.getContext().getSystemService(AlarmManager.class);

        mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
            @Override
            public void onCarrierConfigChanged() {
@@ -996,6 +1018,19 @@ public class DataRetryManager extends Handler {
        mRil.registerForOn(this, EVENT_RADIO_ON, null);
        mRil.registerForModemReset(this, EVENT_MODEM_RESET, null);

        // Register intent of alarm manager for long retry timer
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_RETRY);
        mPhone.getContext().registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (ACTION_RETRY.equals(intent.getAction())) {
                    DataRetryManager.this.onAlarmIntentRetry(
                            intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE, -1 /*Bad hashcode*/));
                }
            }
        }, intentFilter);

        if (mDataConfigManager.shouldResetDataThrottlingWhenTacChanges()) {
            mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED,
                    null);
@@ -1409,20 +1444,50 @@ public class DataRetryManager extends Handler {
     * @param dataRetryEntry The data retry entry.
     */
    private void schedule(@NonNull DataRetryEntry dataRetryEntry) {
        logl("Scheduled data retry: " + dataRetryEntry);
        logl("Scheduled data retry " + dataRetryEntry
                + " hashcode=" + dataRetryEntry.hashCode());
        mDataRetryEntries.add(dataRetryEntry);
        if (mDataRetryEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
            // Discard the oldest retry entry.
            mDataRetryEntries.remove(0);
        }

        // Using delayed message instead of alarm manager to schedule data retry is intentional.
        // When the device enters doze mode, the handler message might be extremely delayed than the
        // original scheduled time. There is no need to wake up the device to perform data retry in
        // that case.
        // When the device is in doze mode, the handler message might be extremely delayed because
        // handler uses relative system time(not counting sleep) which is inaccurate even when we
        // enter the maintenance window.
        // Therefore, we use alarm manager when we need to schedule long timers.
        if (dataRetryEntry.retryDelayMillis <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) {
            sendMessageDelayed(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
                            ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry),
                    dataRetryEntry.retryDelayMillis);
        } else {
            Intent intent = new Intent(ACTION_RETRY);
            intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
            // No need to wake up the device at the exact time, the retry can wait util next time
            // the device wake up to save power.
            mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
                    dataRetryEntry.retryElapsedTime,
                    PendingIntent.getBroadcast(mPhone.getContext(),
                            dataRetryEntry.hashCode() /*Unique identifier of this retry attempt*/,
                            intent,
                            PendingIntent.FLAG_IMMUTABLE));
        }
    }

    /**
     * Called when it's time to retry scheduled by Alarm Manager.
     * @param retryHashcode The hashcode is the unique identifier of which retry entry to retry.
     */
    private void onAlarmIntentRetry(int retryHashcode) {
        DataRetryEntry dataRetryEntry = mDataRetryEntries.stream()
                .filter(entry -> entry.hashCode() == retryHashcode)
                .findAny()
                .orElse(null);
        logl("onAlarmIntentRetry: found " + dataRetryEntry + " with hashcode " + retryHashcode);
        if (dataRetryEntry != null) {
            sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
                    ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry));
        }
    }

    /**
@@ -1636,12 +1701,14 @@ public class DataRetryManager extends Handler {
     */
    public boolean isSimilarNetworkRequestRetryScheduled(
            @NonNull TelephonyNetworkRequest networkRequest, @TransportType int transport) {
        long now = SystemClock.elapsedRealtime();
        for (int i = mDataRetryEntries.size() - 1; i >= 0; i--) {
            if (mDataRetryEntries.get(i) instanceof DataSetupRetryEntry) {
                DataSetupRetryEntry entry = (DataSetupRetryEntry) mDataRetryEntries.get(i);
                if (entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED
                        && entry.setupRetryType
                        == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS) {
                        == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS
                        && entry.retryElapsedTime > now) {
                    if (entry.networkRequestList.isEmpty()) {
                        String msg = "Invalid data retry entry detected";
                        logl(msg);
+2 −0
Original line number Diff line number Diff line
@@ -356,6 +356,8 @@ public class ContextFixture implements TestFixture<Context> {
                return Context.POWER_SERVICE;
            } else if (serviceClass == EuiccManager.class) {
                return Context.EUICC_SERVICE;
            } else if (serviceClass == AlarmManager.class) {
                return Context.ALARM_SERVICE;
            }
            return super.getSystemServiceName(serviceClass);
        }
+8 −1
Original line number Diff line number Diff line
@@ -716,7 +716,14 @@ public abstract class TelephonyTest {
        doReturn(mPhone).when(mInboundSmsHandler).getPhone();
        doReturn(mImsCallProfile).when(mImsCall).getCallProfile();
        doReturn(mIBinder).when(mIIntentSender).asBinder();
        doReturn(mIIntentSender).when(mIActivityManager).getIntentSenderWithFeature(anyInt(),
        doAnswer(invocation -> {
            Intent[] intents = invocation.getArgument(6);
            if (intents != null && intents.length > 0) {
                doReturn(intents[0]).when(mIActivityManager)
                        .getIntentForIntentSender(mIIntentSender);
            }
            return mIIntentSender;
        }).when(mIActivityManager).getIntentSenderWithFeature(anyInt(),
                nullable(String.class), nullable(String.class), nullable(IBinder.class),
                nullable(String.class), anyInt(), nullable(Intent[].class),
                nullable(String[].class), anyInt(), nullable(Bundle.class), anyInt());
+3 −2
Original line number Diff line number Diff line
@@ -2663,8 +2663,9 @@ public class DataNetworkControllerTest extends TelephonyTest {
                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
        processAllFutureMessages();

        // Should retried 20 times, which is the maximum based on the retry config rules.
        verify(mMockedWwanDataServiceManager, times(21)).setupDataCall(anyInt(),
        // The first 8 retries are short timers that scheduled by handler, future retries are
        // scheduled by intent and require more complex mock, so we only verify the first 8 here.
        verify(mMockedWwanDataServiceManager, times(9)).setupDataCall(anyInt(),
                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
                any(), any(), anyBoolean(), any(Message.class));
    }
+46 −1
Original line number Diff line number Diff line
@@ -24,12 +24,17 @@ import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
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.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.AsyncResult;
@@ -130,6 +135,7 @@ public class DataRetryManagerTest extends TelephonyTest {
            .build();

    // Mocked classes
    private AlarmManager mAlarmManager;
    private DataRetryManagerCallback mDataRetryManagerCallbackMock;

    private DataRetryManager mDataRetryManagerUT;
@@ -147,6 +153,7 @@ public class DataRetryManagerTest extends TelephonyTest {
            ((Runnable) invocation.getArguments()[0]).run();
            return null;
        }).when(mDataRetryManagerCallbackMock).invokeFromExecutor(any(Runnable.class));
        mAlarmManager = Mockito.mock(AlarmManager.class);
        SparseArray<DataServiceManager> mockedDataServiceManagers = new SparseArray<>();
        mockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                mMockedWwanDataServiceManager);
@@ -166,6 +173,8 @@ public class DataRetryManagerTest extends TelephonyTest {
                dataNetworkControllerCallbackCaptor.capture());
        mDataNetworkControllerCallback = dataNetworkControllerCallbackCaptor.getValue();

        replaceInstance(DataRetryManager.class, "mAlarmManager",
                mDataRetryManagerUT, mAlarmManager);
        logd("DataRetryManagerTest -Setup!");
    }

@@ -497,7 +506,6 @@ public class DataRetryManagerTest extends TelephonyTest {
        processAllMessages();

        NetworkRequest request = new NetworkRequest.Builder()

                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .build();
        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
@@ -739,6 +747,43 @@ public class DataRetryManagerTest extends TelephonyTest {
        assertThat(rule.getRetryIntervalsMillis()).containsExactly(5000L);
    }

    @Test
    public void testDataRetryLongTimer() {
        // Rule requires a long timer
        DataSetupRetryRule retryRule = new DataSetupRetryRule(
                "capabilities=internet, retry_interval=120000, maximum_retries=2");
        doReturn(Collections.singletonList(retryRule)).when(mDataConfigManager)
                .getDataSetupRetryRules();
        mDataConfigManagerCallback.onCarrierConfigChanged();
        processAllMessages();

        NetworkRequest request = new NetworkRequest.Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .build();
        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
        DataNetworkController.NetworkRequestList
                networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
        mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, networkRequestList, 2253,
                DataCallResponse.RETRY_DURATION_UNDEFINED);
        processAllFutureMessages();

        // Verify scheduled via Alarm Manager
        ArgumentCaptor<PendingIntent> pendingIntentArgumentCaptor =
                ArgumentCaptor.forClass(PendingIntent.class);
        verify(mAlarmManager).setAndAllowWhileIdle(anyInt(), anyLong(),
                pendingIntentArgumentCaptor.capture());

        // Verify starts retry attempt after receiving intent
        PendingIntent pendingIntent = pendingIntentArgumentCaptor.getValue();
        Intent intent = pendingIntent.getIntent();
        mContext.sendBroadcast(intent);
        processAllFutureMessages();

        verify(mDataRetryManagerCallbackMock)
                .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class));
    }

    @Test
    public void testDataHandoverRetryInvalidRulesFromString() {
        assertThrows(IllegalArgumentException.class,