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

Commit 66fd0fe4 authored by Hunsuk Choi's avatar Hunsuk Choi Committed by Android (Google) Code Review
Browse files

Merge "Delay dialing normal routing emergency number in airplane mode" into udc-dev

parents 150a6f06 e1785ca9
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -1026,14 +1026,19 @@ public class EmergencyStateTracker {
                }

                @Override
                public boolean isOkToCall(Phone phone, int serviceState) {
                public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
                    // We currently only look to make sure that the radio is on before dialing. We
                    // should be able to make emergency calls at any time after the radio has been
                    // powered on and isn't in the UNAVAILABLE state, even if it is reporting the
                    // OUT_OF_SERVICE state.
                    return phone.getServiceStateTracker().isRadioOn();
                }
            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber);

                @Override
                public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
                    return true;
                }
            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, 0);
        } else {
            switchDdsAndSetEmergencyMode(phone, emergencyType);
        }
+14 −4
Original line number Diff line number Diff line
@@ -82,7 +82,8 @@ public class RadioOnHelper implements RadioOnStateListener.Callback {
     * and runs on the main looper.)
     */
    public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback,
            boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber) {
            boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber,
            int emergencyTimeoutIntervalMillis) {
        setupListeners();
        mCallback = callback;
        mInProgressListeners.clear();
@@ -93,9 +94,11 @@ public class RadioOnHelper implements RadioOnStateListener.Callback {
                continue;
            }

            int timeoutCallbackInterval = (forEmergencyCall && phone == phoneForEmergencyCall)
                    ? emergencyTimeoutIntervalMillis : 0;
            mInProgressListeners.add(mListeners.get(i));
            mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
                    && phone == phoneForEmergencyCall);
                    && phone == phoneForEmergencyCall, timeoutCallbackInterval);
        }
        powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber);
    }
@@ -152,7 +155,14 @@ public class RadioOnHelper implements RadioOnStateListener.Callback {
    }

    @Override
    public boolean isOkToCall(Phone phone, int serviceState) {
        return (mCallback == null) ? false : mCallback.isOkToCall(phone, serviceState);
    public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
        return (mCallback == null)
                ? false : mCallback.isOkToCall(phone, serviceState, imsVoiceCapable);
    }

    @Override
    public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
        return (mCallback == null)
                ? false : mCallback.onTimeout(phone, serviceState, imsVoiceCapable);
    }
}
+126 −11
Original line number Diff line number Diff line
@@ -42,10 +42,30 @@ public class RadioOnStateListener {
        void onComplete(RadioOnStateListener listener, boolean isRadioReady);

        /**
         * Given the Phone and the new service state of that phone, return whether or not this phone
         * is ok to call. If it is, onComplete will be called shortly after.
         * Returns whether or not this phone is ok to call.
         * If it is, onComplete will be called shortly after.
         *
         * @param phone The Phone associated.
         * @param serviceState The service state of that phone.
         * @param imsVoiceCapable The IMS voice capability of that phone.
         * @return {@code true} if this phone is ok to call. Otherwise, {@code false}.
         */
        boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable);

        /**
         * Returns whether or not this phone is ok to call.
         * This callback will be called when timeout happens.
         * If this returns {@code true}, onComplete will be called shortly after.
         * Otherwise, a new timer will be started again to keep waiting for next timeout.
         * The timeout interval will be passed to {@link #waitForRadioOn()} when registering
         * this callback.
         *
         * @param phone The Phone associated.
         * @param serviceState The service state of that phone.
         * @param imsVoiceCapable The IMS voice capability of that phone.
         * @return {@code true} if this phone is ok to call. Otherwise, {@code false}.
         */
        boolean isOkToCall(Phone phone, int serviceState);
        boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable);
    }

    private static final String TAG = "RadioOnStateListener";
@@ -64,6 +84,8 @@ public class RadioOnStateListener {
    @VisibleForTesting
    public static final int MSG_RADIO_ON = 4;
    public static final int MSG_RADIO_OFF_OR_NOT_AVAILABLE = 5;
    public static final int MSG_IMS_CAPABILITY_CHANGED = 6;
    public static final int MSG_TIMEOUT_ONTIMEOUT_CALLBACK = 7;

    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
@@ -77,8 +99,9 @@ public class RadioOnStateListener {
                                (RadioOnStateListener.Callback) args.arg2;
                        boolean forEmergencyCall = (boolean) args.arg3;
                        boolean isSelectedPhoneForEmergencyCall = (boolean) args.arg4;
                        int onTimeoutCallbackInterval = args.argi1;
                        startSequenceInternal(phone, callback, forEmergencyCall,
                                isSelectedPhoneForEmergencyCall);
                                isSelectedPhoneForEmergencyCall, onTimeoutCallbackInterval);
                    } finally {
                        args.recycle();
                    }
@@ -95,6 +118,11 @@ public class RadioOnStateListener {
                case MSG_RETRY_TIMEOUT:
                    onRetryTimeout();
                    break;
                case MSG_IMS_CAPABILITY_CHANGED:
                    onImsCapabilityChanged();
                    break;
                case MSG_TIMEOUT_ONTIMEOUT_CALLBACK:
                    onTimeoutCallbackTimeout();
                default:
                    Rlog.w(TAG, String.format(Locale.getDefault(),
                        "handleMessage: unexpected message: %d.", msg.what));
@@ -110,6 +138,7 @@ public class RadioOnStateListener {
    // mForEmergencyCall is true.
    private boolean mSelectedPhoneForEmergencyCall;
    private int mNumRetriesSoFar;
    private int mOnTimeoutCallbackInterval; // the interval between onTimeout callbacks

    /**
     * Starts the "wait for radio" sequence. This is the (single) external API of the
@@ -126,7 +155,8 @@ public class RadioOnStateListener {
     * serialized, and runs only on the handler thread.)
     */
    public void waitForRadioOn(Phone phone, Callback callback,
            boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall) {
            boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall,
            int onTimeoutCallbackInverval) {
        Rlog.d(TAG, "waitForRadioOn: Phone " + phone.getPhoneId());

        if (mPhone != null) {
@@ -139,6 +169,7 @@ public class RadioOnStateListener {
        args.arg2 = callback;
        args.arg3 = forEmergencyCall;
        args.arg4 = isSelectedPhoneForEmergencyCall;
        args.argi1 = onTimeoutCallbackInverval;
        mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
    }

@@ -148,7 +179,8 @@ public class RadioOnStateListener {
     * @see #waitForRadioOn
     */
    private void startSequenceInternal(Phone phone, Callback callback,
            boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall) {
            boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall,
            int onTimeoutCallbackInterval) {
        Rlog.d(TAG, "startSequenceInternal: Phone " + phone.getPhoneId());

        // First of all, clean up any state left over from a prior RadioOn call sequence. This
@@ -160,6 +192,7 @@ public class RadioOnStateListener {
        mCallback = callback;
        mForEmergencyCall = forEmergencyCall;
        mSelectedPhoneForEmergencyCall = isSelectedPhoneForEmergencyCall;
        mOnTimeoutCallbackInterval = onTimeoutCallbackInterval;

        registerForServiceStateChanged();
        // Register for RADIO_OFF to handle cases where emergency call is dialed before
@@ -169,6 +202,49 @@ public class RadioOnStateListener {
        // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry
        // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason.
        startRetryTimer();
        registerForImsCapabilityChanged();
        startOnTimeoutCallbackTimer();
    }

    private void onImsCapabilityChanged() {
        if (mPhone == null) {
            return;
        }

        boolean imsVoiceCapable = mPhone.isVoiceOverCellularImsEnabled();

        Rlog.d(TAG, String.format("onImsCapabilityChanged, capable = %s, Phone = %s",
                imsVoiceCapable, mPhone.getPhoneId()));

        if (isOkToCall(mPhone.getServiceState().getState(), imsVoiceCapable)) {
            Rlog.d(TAG, "onImsCapabilityChanged: ok to call!");

            onComplete(true);
            cleanup();
        } else {
            // The IMS capability changed, but we're still not ready to call yet.
            Rlog.d(TAG, "onImsCapabilityChanged: not ready to call yet, keep waiting.");
        }
    }

    private void onTimeoutCallbackTimeout() {
        if (mPhone == null) {
            return;
        }

        if (onTimeout(mPhone.getServiceState().getState(),
                  mPhone.isVoiceOverCellularImsEnabled())) {
            Rlog.d(TAG, "onTimeout: ok to call!");

            onComplete(true);
            cleanup();
        } else if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
            Rlog.w(TAG, "onTimeout: Hit MAX_NUM_RETRIES; giving up.");
            cleanup();
        } else {
            Rlog.d(TAG, "onTimeout: not ready to call yet, keep waiting.");
            startOnTimeoutCallbackTimer();
        }
    }

    /**
@@ -190,7 +266,7 @@ public class RadioOnStateListener {
        // - STATE_EMERGENCY_ONLY    // Only emergency numbers are allowed; currently not used
        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)

        if (isOkToCall(state.getState())) {
        if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) {
            // Woo hoo! It's OK to actually place the call.
            Rlog.d(TAG, "onServiceStateChanged: ok to call!");

@@ -208,7 +284,7 @@ public class RadioOnStateListener {
        }
        ServiceState state = mPhone.getServiceState();
        Rlog.d(TAG, String.format("onRadioOn, state = %s, Phone = %s", state, mPhone.getPhoneId()));
        if (isOkToCall(state.getState())) {
        if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) {
            onComplete(true);
            cleanup();
        } else {
@@ -219,8 +295,17 @@ public class RadioOnStateListener {
    /**
     * Callback to see if it is okay to call yet, given the current conditions.
     */
    private boolean isOkToCall(int serviceState) {
        return (mCallback == null) ? false : mCallback.isOkToCall(mPhone, serviceState);
    private boolean isOkToCall(int serviceState, boolean imsVoiceCapable) {
        return (mCallback == null)
                ? false : mCallback.isOkToCall(mPhone, serviceState, imsVoiceCapable);
    }

    /**
     * Callback to see if it is okay to call yet, given the current conditions.
     */
    private boolean onTimeout(int serviceState, boolean imsVoiceCapable) {
        return (mCallback == null)
                ? false : mCallback.onTimeout(mPhone, serviceState, imsVoiceCapable);
    }

    /**
@@ -242,7 +327,7 @@ public class RadioOnStateListener {
        //   call.
        // - If the radio is still powered off, try powering it on again.

        if (isOkToCall(serviceState)) {
        if (isOkToCall(serviceState, mPhone.isVoiceOverCellularImsEnabled())) {
            Rlog.d(TAG, "onRetryTimeout: Radio is on. Cleaning up.");

            // Woo hoo -- we successfully got out of airplane mode.
@@ -256,6 +341,10 @@ public class RadioOnStateListener {
            Rlog.d(TAG, "mNumRetriesSoFar is now " + mNumRetriesSoFar);

            if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
                if (mHandler.hasMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK)) {
                    Rlog.w(TAG, "Hit MAX_NUM_RETRIES; waiting onTimeout callback");
                    return;
                }
                Rlog.w(TAG, "Hit MAX_NUM_RETRIES; giving up.");
                cleanup();
            } else {
@@ -295,10 +384,12 @@ public class RadioOnStateListener {
        unregisterForRadioOff();
        unregisterForRadioOn();
        cancelRetryTimer();
        unregisterForImsCapabilityChanged();

        // Used for unregisterForServiceStateChanged() so we null it out here instead.
        mPhone = null;
        mNumRetriesSoFar = 0;
        mOnTimeoutCallbackInterval = 0;
    }

    private void startRetryTimer() {
@@ -351,6 +442,30 @@ public class RadioOnStateListener {
        mHandler.removeMessages(MSG_RADIO_ON); // Clean up any pending messages too
    }

    private void registerForImsCapabilityChanged() {
        unregisterForImsCapabilityChanged();
        mPhone.getServiceStateTracker()
                .registerForImsCapabilityChanged(mHandler, MSG_IMS_CAPABILITY_CHANGED, null);
    }

    private void unregisterForImsCapabilityChanged() {
        if (mPhone != null) {
            mPhone.getServiceStateTracker()
                    .unregisterForImsCapabilityChanged(mHandler);
        }
        mHandler.removeMessages(MSG_IMS_CAPABILITY_CHANGED);
    }

    private void startOnTimeoutCallbackTimer() {
        Rlog.d(TAG, "startOnTimeoutCallbackTimer: mOnTimeoutCallbackInterval="
                + mOnTimeoutCallbackInterval);
        mHandler.removeMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK);
        if (mOnTimeoutCallbackInterval > 0) {
            mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT_ONTIMEOUT_CALLBACK,
                    mOnTimeoutCallbackInterval);
        }
    }

    private void onComplete(boolean isRadioReady) {
        if (mCallback != null) {
            Callback tempCallback = mCallback;
+8 −6
Original line number Diff line number Diff line
@@ -157,11 +157,13 @@ public class EmergencyStateTrackerTest extends TelephonyTest {
        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                .forClass(RadioOnStateListener.Callback.class);
        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
                eq(false));
                eq(false), eq(0));
        // isOkToCall() should return true once radio is on
        assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
        assertFalse(callback.getValue()
                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
        when(mSST.isRadioOn()).thenReturn(true);
        assertTrue(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
        assertTrue(callback.getValue()
                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
        // Once radio on is complete, trigger delay dial
        callback.getValue().onComplete(null, true);
        ArgumentCaptor<Consumer<Boolean>> completeConsumer = ArgumentCaptor
@@ -194,7 +196,7 @@ public class EmergencyStateTrackerTest extends TelephonyTest {
        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                .forClass(RadioOnStateListener.Callback.class);
        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
                eq(false));
                eq(false), eq(0));
        // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
        CompletableFuture<Void> unused = future.thenAccept((result) -> {
            assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF);
@@ -222,7 +224,7 @@ public class EmergencyStateTrackerTest extends TelephonyTest {

        // Radio already on so shouldn't trigger this
        verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
                anyBoolean());
                anyBoolean(), eq(0));
        // Carrier supports control-plane fallback, so no DDS switch
        verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
    }
+44 −15
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -64,6 +65,7 @@ public class RadioOnStateListenerTest extends TelephonyTest {
        super.setUp(getClass().getSimpleName());
        MockitoAnnotations.initMocks(this);
        mListener = new RadioOnStateListener();
        doReturn(mSST).when(mMockPhone).getServiceStateTracker();
    }

    @After
@@ -83,7 +85,7 @@ public class RadioOnStateListenerTest extends TelephonyTest {
    @Test
    public void testRegisterForCallback() {
        mMockPhone.mCi = mMockCi;
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false);
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);

        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);

@@ -96,8 +98,9 @@ public class RadioOnStateListenerTest extends TelephonyTest {
    }

    /**
     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int)} returns true, so we are
     * expecting {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to
     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns true,
     * so we are expecting
     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to
     * return true.
     */
    @Test
@@ -106,9 +109,10 @@ public class RadioOnStateListenerTest extends TelephonyTest {
        state.setState(ServiceState.STATE_IN_SERVICE);
        when(mMockPhone.getServiceState()).thenReturn(state);
        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt())).thenReturn(true);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean()))
                .thenReturn(true);
        mMockPhone.mCi = mMockCi;
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false);
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);

        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
@@ -121,17 +125,18 @@ public class RadioOnStateListenerTest extends TelephonyTest {
    /**
     * We never receive a
     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} because
     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int)} returns false.
     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns false.
     */
    @Test
    public void testPhoneChangeState_NoOkToCall_Timeout() {
        ServiceState state = new ServiceState();
        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt())).thenReturn(false);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean()))
                .thenReturn(false);
        when(mMockPhone.getServiceState()).thenReturn(state);
        mMockPhone.mCi = mMockCi;
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false);
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);

        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
@@ -142,8 +147,8 @@ public class RadioOnStateListenerTest extends TelephonyTest {
    }

    /**
     * Tests {@link RadioOnStateListener.Callback#isOkToCall(Phone, int)} returning false and
     * hitting the max number of retries. This should result in
     * Tests {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returning
     * false and hitting the max number of retries. This should result in
     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} returning
     * false.
     */
@@ -153,13 +158,14 @@ public class RadioOnStateListenerTest extends TelephonyTest {
        state.setState(ServiceState.STATE_POWER_OFF);
        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
        when(mMockPhone.getServiceState()).thenReturn(state);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt())).thenReturn(false);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean()))
                .thenReturn(false);
        mListener.setTimeBetweenRetriesMillis(0/* ms */);
        mListener.setMaxNumRetries(2);

        // Wait for the timer to expire and check state manually in onRetryTimeout
        mMockPhone.mCi = mMockCi;
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false);
        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS);

        verify(mCallback).onComplete(eq(mListener), eq(false));
@@ -172,16 +178,39 @@ public class RadioOnStateListenerTest extends TelephonyTest {
        state.setState(ServiceState.STATE_POWER_OFF);
        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
        when(mMockPhone.getServiceState()).thenReturn(state);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt())).thenReturn(false);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean()))
                .thenReturn(false);
        mListener.setTimeBetweenRetriesMillis(0/* ms */);
        mListener.setMaxNumRetries(2);

        // Wait for the timer to expire and check state manually in onRetryTimeout
        mMockPhone.mCi = mMockCi;
        mListener.waitForRadioOn(mMockPhone, mCallback, true, true);
        mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 0);
        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS );

        verify(mCallback).onComplete(eq(mListener), eq(false));
        verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false));
    }

    @Test
    public void testTimeout_OnTimeoutForEmergency() {
        ServiceState state = new ServiceState();
        state.setState(ServiceState.STATE_POWER_OFF);
        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
        when(mMockPhone.getServiceState()).thenReturn(state);
        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean()))
                .thenReturn(false);
        when(mCallback.onTimeout(eq(mMockPhone), anyInt(), anyBoolean()))
                .thenReturn(true);
        mListener.setTimeBetweenRetriesMillis(0 /* ms */);
        mListener.setMaxNumRetries(1);

        // Wait for the timer to expire and check state manually in onRetryTimeout
        mMockPhone.mCi = mMockCi;
        mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 100);
        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS);

        verify(mCallback).onTimeout(eq(mMockPhone), anyInt(), anyBoolean());
        verify(mCallback).onComplete(eq(mListener), eq(true));
    }
}