Loading src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java +7 −2 Original line number Diff line number Diff line Loading @@ -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); } Loading src/java/com/android/internal/telephony/emergency/RadioOnHelper.java +14 −4 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); } Loading Loading @@ -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); } } src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java +126 −11 Original line number Diff line number Diff line Loading @@ -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"; Loading @@ -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 Loading @@ -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(); } Loading @@ -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)); Loading @@ -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 Loading @@ -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) { Loading @@ -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(); } Loading @@ -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 Loading @@ -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 Loading @@ -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(); } } /** Loading @@ -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!"); Loading @@ -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 { Loading @@ -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); } /** Loading @@ -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. Loading @@ -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 { Loading Loading @@ -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() { Loading Loading @@ -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; Loading tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java +8 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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()); } Loading tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java +44 −15 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); Loading @@ -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 Loading @@ -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, Loading @@ -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, Loading @@ -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. */ Loading @@ -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)); Loading @@ -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)); } } Loading
src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java +7 −2 Original line number Diff line number Diff line Loading @@ -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); } Loading
src/java/com/android/internal/telephony/emergency/RadioOnHelper.java +14 −4 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); } Loading Loading @@ -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); } }
src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java +126 −11 Original line number Diff line number Diff line Loading @@ -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"; Loading @@ -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 Loading @@ -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(); } Loading @@ -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)); Loading @@ -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 Loading @@ -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) { Loading @@ -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(); } Loading @@ -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 Loading @@ -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 Loading @@ -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(); } } /** Loading @@ -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!"); Loading @@ -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 { Loading @@ -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); } /** Loading @@ -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. Loading @@ -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 { Loading Loading @@ -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() { Loading Loading @@ -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; Loading
tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java +8 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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()); } Loading
tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java +44 −15 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); Loading @@ -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 Loading @@ -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, Loading @@ -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, Loading @@ -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. */ Loading @@ -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)); Loading @@ -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)); } }