Loading src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java +128 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.telephony.emergency; import static android.telecom.Connection.STATE_ACTIVE; import static android.telecom.Connection.STATE_DISCONNECTED; import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL; import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK; Loading Loading @@ -53,12 +55,15 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Call; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.Connection; import com.android.internal.telephony.GsmCdmaPhone; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.data.PhoneSwitcher; import com.android.internal.telephony.imsphone.ImsPhoneConnection; import com.android.internal.telephony.satellite.SatelliteController; import com.android.telephony.Rlog; Loading @@ -77,6 +82,19 @@ public class EmergencyStateTracker { private static final String TAG = "EmergencyStateTracker"; private static class OnDisconnectListener extends Connection.ListenerBase { private final CompletableFuture<Boolean> mFuture; OnDisconnectListener(CompletableFuture<Boolean> future) { mFuture = future; } @Override public void onDisconnect(int cause) { mFuture.complete(true); } }; /** * Timeout before we continue with the emergency call without waiting for DDS switch response * from the modem. Loading @@ -91,6 +109,9 @@ public class EmergencyStateTracker { private static final int DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS = 1 * 1000; // Timeout to wait for the termination of incoming call before continue with the emergency call. private static final int DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS = 3 * 1000; // 3 seconds. /** The emergency types used when setting the emergency mode on modem. */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "EMERGENCY_TYPE_", Loading Loading @@ -149,6 +170,7 @@ public class EmergencyStateTracker { private boolean mIsEmergencySmsStartedDuringScbm; private CompletableFuture<Boolean> mEmergencyTransportChangedFuture; private final Object mRegistrantidentifier = new Object(); private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported = new android.util.ArrayMap<>(); Loading Loading @@ -233,6 +255,8 @@ public class EmergencyStateTracker { public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3; /** A message which is used to automatically exit from SCBM after a period of time. */ private static final int MSG_EXIT_SCBM = 4; @VisibleForTesting public static final int MSG_NEW_RINGING_CONNECTION = 5; private class MyHandler extends Handler { Loading Loading @@ -377,6 +401,10 @@ public class EmergencyStateTracker { exitEmergencySmsCallbackModeAndEmergencyMode(); break; } case MSG_NEW_RINGING_CONNECTION: { handleNewRingingConnection(msg); break; } default: break; } Loading Loading @@ -436,6 +464,8 @@ public class EmergencyStateTracker { filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); context.registerReceiver(mEcmExitReceiver, filter, null, mHandler); mTelephonyManagerProxy = new TelephonyManagerProxyImpl(context); registerForNewRingingConnection(); } /** Loading Loading @@ -471,6 +501,7 @@ public class EmergencyStateTracker { IntentFilter filter = new IntentFilter(); filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); context.registerReceiver(mEcmExitReceiver, filter, null, mHandler); registerForNewRingingConnection(); } /** Loading Loading @@ -550,6 +581,7 @@ public class EmergencyStateTracker { mPhone = phone; mOngoingConnection = c; mIsTestEmergencyNumber = isTestEmergencyNumber; maybeRejectIncomingCall(null); return mCallEmergencyModeFuture; } } Loading @@ -557,7 +589,10 @@ public class EmergencyStateTracker { mPhone = phone; mOngoingConnection = c; mIsTestEmergencyNumber = isTestEmergencyNumber; maybeRejectIncomingCall(result -> { Rlog.i(TAG, "maybeRejectIncomingCall : result = " + result); turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber); }); return mCallEmergencyModeFuture; } Loading Loading @@ -1723,4 +1758,96 @@ public class EmergencyStateTracker { Rlog.i(TAG, "updateNoSimEcbmSupported preference updated slotIndex=" + slotIndex + ", supported=" + carrierConfig); } /** * Ensures that there is no incoming call. * * @param completeConsumer The consumer to call once rejecting incoming call completes, * provides {@code true} result if operation completes successfully * or {@code false} if the operation timed out/failed. */ private void maybeRejectIncomingCall(Consumer<Boolean> completeConsumer) { Phone[] phones = mPhoneFactoryProxy.getPhones(); if (phones == null) { if (completeConsumer != null) { completeConsumer.accept(true); } return; } Call ringingCall = null; for (Phone phone : phones) { ringingCall = phone.getRingingCall(); if (ringingCall != null && ringingCall.isRinging()) { Rlog.i(TAG, "maybeRejectIncomingCall found a ringing call"); break; } } if (ringingCall == null || !ringingCall.isRinging()) { if (completeConsumer != null) { completeConsumer.accept(true); } return; } try { ringingCall.hangup(); if (completeConsumer == null) return; CompletableFuture<Boolean> future = new CompletableFuture<>(); com.android.internal.telephony.Connection cn = ringingCall.getLatestConnection(); cn.addListener(new OnDisconnectListener(future)); // A timeout that will complete the future to not block the outgoing call indefinitely. CompletableFuture<Boolean> timeout = new CompletableFuture<>(); mHandler.postDelayed( () -> timeout.complete(false), DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS); // Ensure that the Consumer is completed on the main thread. CompletableFuture<Void> unused = future.acceptEitherAsync(timeout, completeConsumer, mHandler::post).exceptionally((ex) -> { Rlog.w(TAG, "maybeRejectIncomingCall - exceptionally= " + ex); return null; }); } catch (Exception e) { Rlog.w(TAG, "maybeRejectIncomingCall - exception= " + e.getMessage()); if (completeConsumer != null) { completeConsumer.accept(false); } } } private void registerForNewRingingConnection() { Phone[] phones = mPhoneFactoryProxy.getPhones(); if (phones == null) { // unit testing return; } for (Phone phone : phones) { phone.registerForNewRingingConnection(mHandler, MSG_NEW_RINGING_CONNECTION, mRegistrantidentifier); } } /** * Hangup the new ringing call if there is an ongoing emergency call not connected. */ private void handleNewRingingConnection(Message msg) { Connection c = (Connection) ((AsyncResult) msg.obj).result; if (c == null || mOngoingConnection == null || mOngoingConnection.getState() == STATE_ACTIVE || mOngoingConnection.getState() == STATE_DISCONNECTED) { return; } if ((c.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) && ((ImsPhoneConnection) c).isIncomingCallAutoRejected()) { Rlog.i(TAG, "handleNewRingingConnection auto rejected call"); } else { try { Rlog.i(TAG, "handleNewRingingConnection silently drop incoming call"); c.getCall().hangup(); } catch (CallStateException e) { Rlog.w(TAG, "handleNewRingingConnection", e); } } } } tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java +116 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,8 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.telephony.Call; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.Connection; import com.android.internal.telephony.GsmCdmaPhone; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; Loading Loading @@ -2559,6 +2561,120 @@ public class EmergencyStateTrackerTest extends TelephonyTest { verify(testPhone).exitEmergencyMode(any(Message.class)); } /** * Test that the EmergencyStateTracker rejects incoming call when starting an emergency call. */ @Test @SmallTest public void testRejectRingingCall() { Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */, false /* isRadioOn */); when(phone.getSubId()).thenReturn(1); Connection c = mock(Connection.class); Call call = mock(Call.class); doReturn(c).when(call).getLatestConnection(); doReturn(true).when(call).isRinging(); doReturn(call).when(phone).getRingingCall(); setEcmSupportedConfig(phone, true); EmergencyStateTracker testEst = setupEmergencyStateTracker( false /* isSuplDdsSwitchRequiredForEmergencyCall */); // There is an ongoing emergency call. CompletableFuture<Integer> future = testEst.startEmergencyCall(phone, mTestConnection1, false); assertNotNull(future); // Verify rejecting ringing call. try { verify(call).hangup(); } catch (CallStateException e) { } } /** * Test that the EmergencyStateTracker rejects incoming call if there is an emergency call * in dialing state. */ @Test @SmallTest public void testRejectNewIncomingCall() { Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */, false /* isRadioOn */); when(phone.getSubId()).thenReturn(1); setEcmSupportedConfig(phone, true); EmergencyStateTracker testEst = setupEmergencyStateTracker( false /* isSuplDdsSwitchRequiredForEmergencyCall */); ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class); ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class); verify(phone).registerForNewRingingConnection(handlerCaptor.capture(), intCaptor.capture(), any()); assertNotNull(handlerCaptor.getValue()); assertNotNull(intCaptor.getValue()); // There is an ongoing emergency call. CompletableFuture<Integer> future = testEst.startEmergencyCall(phone, mTestConnection1, false); assertNotNull(future); Connection c = mock(Connection.class); Call call = mock(Call.class); doReturn(call).when(c).getCall(); Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue()); AsyncResult.forMessage(msg, c, null); msg.sendToTarget(); processAllMessages(); // Verify rejecting incoming call. try { verify(call).hangup(); } catch (CallStateException e) { } } @Test @SmallTest public void testNotRejectNewIncomingCall() { Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */, false /* isRadioOn */); when(phone.getSubId()).thenReturn(1); setEcmSupportedConfig(phone, true); EmergencyStateTracker unused = setupEmergencyStateTracker( false /* isSuplDdsSwitchRequiredForEmergencyCall */); ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class); ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class); verify(phone).registerForNewRingingConnection(handlerCaptor.capture(), intCaptor.capture(), any()); assertNotNull(handlerCaptor.getValue()); assertNotNull(intCaptor.getValue()); // There is no ongoing emergency call. Connection c = mock(Connection.class); Call call = mock(Call.class); doReturn(call).when(c).getCall(); Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue()); AsyncResult.forMessage(msg, c, null); msg.sendToTarget(); processAllMessages(); // Verify not rejecting incoming call. try { verify(call, never()).hangup(); } catch (CallStateException e) { } } private EmergencyStateTracker setupEmergencyStateTracker( boolean isSuplDdsSwitchRequiredForEmergencyCall) { doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher(); Loading Loading
src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java +128 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.telephony.emergency; import static android.telecom.Connection.STATE_ACTIVE; import static android.telecom.Connection.STATE_DISCONNECTED; import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL; import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK; Loading Loading @@ -53,12 +55,15 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Call; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.Connection; import com.android.internal.telephony.GsmCdmaPhone; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.data.PhoneSwitcher; import com.android.internal.telephony.imsphone.ImsPhoneConnection; import com.android.internal.telephony.satellite.SatelliteController; import com.android.telephony.Rlog; Loading @@ -77,6 +82,19 @@ public class EmergencyStateTracker { private static final String TAG = "EmergencyStateTracker"; private static class OnDisconnectListener extends Connection.ListenerBase { private final CompletableFuture<Boolean> mFuture; OnDisconnectListener(CompletableFuture<Boolean> future) { mFuture = future; } @Override public void onDisconnect(int cause) { mFuture.complete(true); } }; /** * Timeout before we continue with the emergency call without waiting for DDS switch response * from the modem. Loading @@ -91,6 +109,9 @@ public class EmergencyStateTracker { private static final int DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS = 1 * 1000; // Timeout to wait for the termination of incoming call before continue with the emergency call. private static final int DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS = 3 * 1000; // 3 seconds. /** The emergency types used when setting the emergency mode on modem. */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "EMERGENCY_TYPE_", Loading Loading @@ -149,6 +170,7 @@ public class EmergencyStateTracker { private boolean mIsEmergencySmsStartedDuringScbm; private CompletableFuture<Boolean> mEmergencyTransportChangedFuture; private final Object mRegistrantidentifier = new Object(); private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported = new android.util.ArrayMap<>(); Loading Loading @@ -233,6 +255,8 @@ public class EmergencyStateTracker { public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3; /** A message which is used to automatically exit from SCBM after a period of time. */ private static final int MSG_EXIT_SCBM = 4; @VisibleForTesting public static final int MSG_NEW_RINGING_CONNECTION = 5; private class MyHandler extends Handler { Loading Loading @@ -377,6 +401,10 @@ public class EmergencyStateTracker { exitEmergencySmsCallbackModeAndEmergencyMode(); break; } case MSG_NEW_RINGING_CONNECTION: { handleNewRingingConnection(msg); break; } default: break; } Loading Loading @@ -436,6 +464,8 @@ public class EmergencyStateTracker { filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); context.registerReceiver(mEcmExitReceiver, filter, null, mHandler); mTelephonyManagerProxy = new TelephonyManagerProxyImpl(context); registerForNewRingingConnection(); } /** Loading Loading @@ -471,6 +501,7 @@ public class EmergencyStateTracker { IntentFilter filter = new IntentFilter(); filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); context.registerReceiver(mEcmExitReceiver, filter, null, mHandler); registerForNewRingingConnection(); } /** Loading Loading @@ -550,6 +581,7 @@ public class EmergencyStateTracker { mPhone = phone; mOngoingConnection = c; mIsTestEmergencyNumber = isTestEmergencyNumber; maybeRejectIncomingCall(null); return mCallEmergencyModeFuture; } } Loading @@ -557,7 +589,10 @@ public class EmergencyStateTracker { mPhone = phone; mOngoingConnection = c; mIsTestEmergencyNumber = isTestEmergencyNumber; maybeRejectIncomingCall(result -> { Rlog.i(TAG, "maybeRejectIncomingCall : result = " + result); turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber); }); return mCallEmergencyModeFuture; } Loading Loading @@ -1723,4 +1758,96 @@ public class EmergencyStateTracker { Rlog.i(TAG, "updateNoSimEcbmSupported preference updated slotIndex=" + slotIndex + ", supported=" + carrierConfig); } /** * Ensures that there is no incoming call. * * @param completeConsumer The consumer to call once rejecting incoming call completes, * provides {@code true} result if operation completes successfully * or {@code false} if the operation timed out/failed. */ private void maybeRejectIncomingCall(Consumer<Boolean> completeConsumer) { Phone[] phones = mPhoneFactoryProxy.getPhones(); if (phones == null) { if (completeConsumer != null) { completeConsumer.accept(true); } return; } Call ringingCall = null; for (Phone phone : phones) { ringingCall = phone.getRingingCall(); if (ringingCall != null && ringingCall.isRinging()) { Rlog.i(TAG, "maybeRejectIncomingCall found a ringing call"); break; } } if (ringingCall == null || !ringingCall.isRinging()) { if (completeConsumer != null) { completeConsumer.accept(true); } return; } try { ringingCall.hangup(); if (completeConsumer == null) return; CompletableFuture<Boolean> future = new CompletableFuture<>(); com.android.internal.telephony.Connection cn = ringingCall.getLatestConnection(); cn.addListener(new OnDisconnectListener(future)); // A timeout that will complete the future to not block the outgoing call indefinitely. CompletableFuture<Boolean> timeout = new CompletableFuture<>(); mHandler.postDelayed( () -> timeout.complete(false), DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS); // Ensure that the Consumer is completed on the main thread. CompletableFuture<Void> unused = future.acceptEitherAsync(timeout, completeConsumer, mHandler::post).exceptionally((ex) -> { Rlog.w(TAG, "maybeRejectIncomingCall - exceptionally= " + ex); return null; }); } catch (Exception e) { Rlog.w(TAG, "maybeRejectIncomingCall - exception= " + e.getMessage()); if (completeConsumer != null) { completeConsumer.accept(false); } } } private void registerForNewRingingConnection() { Phone[] phones = mPhoneFactoryProxy.getPhones(); if (phones == null) { // unit testing return; } for (Phone phone : phones) { phone.registerForNewRingingConnection(mHandler, MSG_NEW_RINGING_CONNECTION, mRegistrantidentifier); } } /** * Hangup the new ringing call if there is an ongoing emergency call not connected. */ private void handleNewRingingConnection(Message msg) { Connection c = (Connection) ((AsyncResult) msg.obj).result; if (c == null || mOngoingConnection == null || mOngoingConnection.getState() == STATE_ACTIVE || mOngoingConnection.getState() == STATE_DISCONNECTED) { return; } if ((c.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) && ((ImsPhoneConnection) c).isIncomingCallAutoRejected()) { Rlog.i(TAG, "handleNewRingingConnection auto rejected call"); } else { try { Rlog.i(TAG, "handleNewRingingConnection silently drop incoming call"); c.getCall().hangup(); } catch (CallStateException e) { Rlog.w(TAG, "handleNewRingingConnection", e); } } } }
tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java +116 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,8 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.telephony.Call; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.Connection; import com.android.internal.telephony.GsmCdmaPhone; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; Loading Loading @@ -2559,6 +2561,120 @@ public class EmergencyStateTrackerTest extends TelephonyTest { verify(testPhone).exitEmergencyMode(any(Message.class)); } /** * Test that the EmergencyStateTracker rejects incoming call when starting an emergency call. */ @Test @SmallTest public void testRejectRingingCall() { Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */, false /* isRadioOn */); when(phone.getSubId()).thenReturn(1); Connection c = mock(Connection.class); Call call = mock(Call.class); doReturn(c).when(call).getLatestConnection(); doReturn(true).when(call).isRinging(); doReturn(call).when(phone).getRingingCall(); setEcmSupportedConfig(phone, true); EmergencyStateTracker testEst = setupEmergencyStateTracker( false /* isSuplDdsSwitchRequiredForEmergencyCall */); // There is an ongoing emergency call. CompletableFuture<Integer> future = testEst.startEmergencyCall(phone, mTestConnection1, false); assertNotNull(future); // Verify rejecting ringing call. try { verify(call).hangup(); } catch (CallStateException e) { } } /** * Test that the EmergencyStateTracker rejects incoming call if there is an emergency call * in dialing state. */ @Test @SmallTest public void testRejectNewIncomingCall() { Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */, false /* isRadioOn */); when(phone.getSubId()).thenReturn(1); setEcmSupportedConfig(phone, true); EmergencyStateTracker testEst = setupEmergencyStateTracker( false /* isSuplDdsSwitchRequiredForEmergencyCall */); ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class); ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class); verify(phone).registerForNewRingingConnection(handlerCaptor.capture(), intCaptor.capture(), any()); assertNotNull(handlerCaptor.getValue()); assertNotNull(intCaptor.getValue()); // There is an ongoing emergency call. CompletableFuture<Integer> future = testEst.startEmergencyCall(phone, mTestConnection1, false); assertNotNull(future); Connection c = mock(Connection.class); Call call = mock(Call.class); doReturn(call).when(c).getCall(); Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue()); AsyncResult.forMessage(msg, c, null); msg.sendToTarget(); processAllMessages(); // Verify rejecting incoming call. try { verify(call).hangup(); } catch (CallStateException e) { } } @Test @SmallTest public void testNotRejectNewIncomingCall() { Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */, false /* isRadioOn */); when(phone.getSubId()).thenReturn(1); setEcmSupportedConfig(phone, true); EmergencyStateTracker unused = setupEmergencyStateTracker( false /* isSuplDdsSwitchRequiredForEmergencyCall */); ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class); ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class); verify(phone).registerForNewRingingConnection(handlerCaptor.capture(), intCaptor.capture(), any()); assertNotNull(handlerCaptor.getValue()); assertNotNull(intCaptor.getValue()); // There is no ongoing emergency call. Connection c = mock(Connection.class); Call call = mock(Call.class); doReturn(call).when(c).getCall(); Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue()); AsyncResult.forMessage(msg, c, null); msg.sendToTarget(); processAllMessages(); // Verify not rejecting incoming call. try { verify(call, never()).hangup(); } catch (CallStateException e) { } } private EmergencyStateTracker setupEmergencyStateTracker( boolean isSuplDdsSwitchRequiredForEmergencyCall) { doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher(); Loading