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

Commit 9bf7ddb8 authored by Hunsuk Choi's avatar Hunsuk Choi
Browse files

Reject incoming call for emergency call

An emergency call fails when receiving a call at the same time.

Case1. Reject incoming call when starting an emergency call.
Case2. Reject incoming call if it receives a new incoming call after
starting an emergency call.

Bug: 328671756
Test: atest EmergencyStateTrackerTest
Manual test:
1. Make a call from A to DUT.
2. Check whether the call is in alerting state in A.
3. At that moment, dial 911 in DUT before the incoming call is notified.

Change-Id: I1bf39b8cad3b0b2709bebdf515d1986913cc653c
parent 61ea56e6
Loading
Loading
Loading
Loading
+128 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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.
@@ -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_",
@@ -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<>();
@@ -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 {

@@ -377,6 +401,10 @@ public class EmergencyStateTracker {
                    exitEmergencySmsCallbackModeAndEmergencyMode();
                    break;
                }
                case MSG_NEW_RINGING_CONNECTION: {
                    handleNewRingingConnection(msg);
                    break;
                }
                default:
                    break;
            }
@@ -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();
    }

    /**
@@ -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();
    }

    /**
@@ -550,6 +581,7 @@ public class EmergencyStateTracker {
                mPhone = phone;
                mOngoingConnection = c;
                mIsTestEmergencyNumber = isTestEmergencyNumber;
                maybeRejectIncomingCall(null);
                return mCallEmergencyModeFuture;
            }
        }
@@ -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;
    }

@@ -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);
            }
        }
    }
}
+116 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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();