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

Commit 472138ca authored by Brad Ebinger's avatar Brad Ebinger
Browse files

Enhance existing ECBM CL to also include domain specific ECBM logic

This CL does a couple of things:
- Cleans up some linting errors
- Speeds up testing the EmergencyStateTracker test by reducing
  timeouts during testing and reducing scope of hidden variables.
- Uses new CarrierConfigManager API vs old one to speed up config
  transactions over Binder
- Fixes a bug where using a test number would cause the Radio response
  to never complete. We also now clear the mIsTestEmergencyNumber
  variable once the emergency modes are exited.
- Enhances the code to take into account the domain that the
  emergency call is currently placed over. This is important for
  ECBM because we perform different procedures based on domain
  selected.
- Added some logging.

Test: atest EmergencyStateTrackerTest; placed/received call/sms/data
Bug: 247639718
Change-Id: I829f757c6a1081218c868abc3eada433a851605a
parent 6cecf339
Loading
Loading
Loading
Loading
+112 −73
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.internal.telephony.emergency;

import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_NONE;
import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;

import android.annotation.NonNull;
@@ -30,6 +29,7 @@ import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
@@ -37,10 +37,10 @@ import android.sysprop.TelephonyProperties;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.EmergencyRegResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
@@ -48,15 +48,12 @@ 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.RIL;
import com.android.internal.telephony.TelephonyCapabilities;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.data.PhoneSwitcher;
import com.android.telephony.Rlog;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -77,38 +74,38 @@ public class EmergencyStateTracker {
    private static final boolean DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED = true;
    /** Default Emergency Callback Mode exit timeout value. */
    private static final long DEFAULT_ECM_EXIT_TIMEOUT_MS = 300000;
    public long defaultEcmExitTimeoutMs = DEFAULT_ECM_EXIT_TIMEOUT_MS;

    private static EmergencyStateTracker INSTANCE = null;

    private final long mEcmExitTimeoutMs;
    private final Context mContext;
    private final CarrierConfigManager mConfigManager;
    private final Handler mHandler;
    private @EmergencyConstants.EmergencyMode int mEmergencyMode = MODE_EMERGENCY_NONE;
    private final boolean mIsSuplDdsSwitchRequiredForEmergencyCall;
    /** Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.*/
    private final Set<String> mActiveEmergencyCalls = new HashSet<>();
    private final PowerManager.WakeLock mWakeLock;

    @EmergencyConstants.EmergencyMode
    private int mEmergencyMode = MODE_EMERGENCY_NONE;
    private Phone mPhone;
    private RadioOnHelper mRadioOnHelper;
    private CompletableFuture<Integer> mOnCompleted = null;
    /** Tracks emergency calls by callId that have reached {@link Call.State.Active}.*/
    private Set<String> mActiveEmergencyCalls = new HashSet();
    private boolean mIsSuplDdsSwitchRequiredForEmergencyCall;
    // Domain of the active emergency call. Assuming here that there will only be one domain active.
    private int mEmergencyCallDomain = -1;
    private EmergencyRegResult mLastEmergencyRegResult;
    private boolean mIsInEmergencyCall = false;
    private boolean mIsTestEmergencyNumber = false;
    private boolean mIsPhoneInEcmState = false;
    private PowerManager.WakeLock mWakeLock;
    private Runnable mOnEcmExitCompleteRunnable = null;

    // A runnable which is used to automatically exit from Ecm after a period of time.
    private Runnable mExitEcmRunnable = new Runnable() {
        @Override
        public void run() {
            exitEmergencyCallbackMode();
        }
    };
    private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;

    /**
     * Listens for Emergency Callback Mode state change intents
     */
    private BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() {
    private final BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(
@@ -132,12 +129,7 @@ public class EmergencyStateTracker {
        Phone[] getPhones();
    }

    private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
        @Override
        public Phone[] getPhones() {
            return PhoneFactory.getPhones();
        }
    };
    private PhoneFactoryProxy mPhoneFactoryProxy = PhoneFactory::getPhones;

    /** PhoneSwitcher dependencies for testing. */
    @VisibleForTesting
@@ -146,12 +138,7 @@ public class EmergencyStateTracker {
        PhoneSwitcher getPhoneSwitcher();
    }

    private PhoneSwitcherProxy mPhoneSwitcherProxy = new PhoneSwitcherProxy() {
        @Override
        public PhoneSwitcher getPhoneSwitcher() {
            return PhoneSwitcher.getInstance();
        }
    };
    private PhoneSwitcherProxy mPhoneSwitcherProxy = PhoneSwitcher::getInstance;

    /**
     * TelephonyManager dependencies for testing.
@@ -161,7 +148,7 @@ public class EmergencyStateTracker {
        int getPhoneCount();
    }

    private TelephonyManagerProxy mTelephonyManagerProxy;
    private final TelephonyManagerProxy mTelephonyManagerProxy;

    private static class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
        private final TelephonyManager mTelephonyManager;
@@ -173,7 +160,7 @@ public class EmergencyStateTracker {

        @Override
        public int getPhoneCount() {
            return mTelephonyManager.getPhoneCount();
            return mTelephonyManager.getActiveModemCount();
        }
    }

@@ -266,12 +253,15 @@ public class EmergencyStateTracker {
     */
    private EmergencyStateTracker(Context context, Looper looper,
            boolean isSuplDdsSwitchRequiredForEmergencyCall) {
        mEcmExitTimeoutMs = DEFAULT_ECM_EXIT_TIMEOUT_MS;
        mContext = context;
        mHandler = new MyHandler(looper);
        mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;

        PowerManager pm = context.getSystemService(PowerManager.class);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "telephony:" + TAG) : null;
        mConfigManager = context.getSystemService(CarrierConfigManager.class);

        // Register receiver for ECM exit.
        IntentFilter filter = new IntentFilter();
@@ -305,9 +295,9 @@ public class EmergencyStateTracker {
        mPhoneSwitcherProxy = phoneSwitcherProxy;
        mTelephonyManagerProxy = telephonyManagerProxy;
        mRadioOnHelper = radioOnHelper;
        defaultEcmExitTimeoutMs = ecmExitTimeoutMs;
        PowerManager pm = context.getSystemService(PowerManager.class);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mEcmExitTimeoutMs = ecmExitTimeoutMs;
        mWakeLock = null; // Don't declare a wakelock in tests
        mConfigManager = context.getSystemService(CarrierConfigManager.class);
        IntentFilter filter = new IntentFilter();
        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
        context.registerReceiver(mEcmExitReceiver, filter);
@@ -327,7 +317,7 @@ public class EmergencyStateTracker {
     */
    public CompletableFuture<Integer> startEmergencyCall(Phone phone, String callId,
            boolean isTestEmergencyNumber) {
        Rlog.i(TAG, "startEmergencyCall");
        Rlog.i(TAG, "startEmergencyCall for callId:" + callId);

        if (mPhone != null) {
            Rlog.e(TAG, "startEmergencyCall failed. Existing emergency call in progress.");
@@ -409,8 +399,14 @@ public class EmergencyStateTracker {
        }
        mEmergencyMode = mode;

        Message m = mHandler.obtainMessage(msg);
        if (mIsTestEmergencyNumber) {
            Rlog.d(TAG, "IsTestEmergencyNumber true. Skipping setting emergency mode on modem.");
            // Send back a response for the command, but with null information
            AsyncResult.forMessage(m, null, null);
            // Ensure that we do not accidentally block indefinitely when trying to validate test
            // emergency numbers
            m.sendToTarget();
            return;
        }
        mPhone.setEmergencyMode(mode, mHandler.obtainMessage(msg));
@@ -452,24 +448,26 @@ public class EmergencyStateTracker {
     *
     * <p>
     * Enter ECM only once all active emergency calls have ended. If a call never reached
     * {@link Call.State.ACTIVE}, then no need to enter ECM.
     * {@link Call.State#ACTIVE}, then no need to enter ECM.
     *
     * @param callId the call id on which to end the emergency call.
     */
    public void endCall(String callId) {
        boolean wasActive = mActiveEmergencyCalls.remove(callId);
        if ((wasActive && emergencyCallbackModeSupported() && mActiveEmergencyCalls.isEmpty())
                || mIsTestEmergencyNumber) {
        if (mIsTestEmergencyNumber
                || (wasActive && emergencyCallbackModeSupported()
                    && mActiveEmergencyCalls.isEmpty())) {
            enterEmergencyCallbackMode();
        } else {
            exitEmergencyMode();
            mEmergencyCallDomain = -1;
        }
    }

    /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */
    public EmergencyRegResult getEmergencyRegResult() {
        return mLastEmergencyRegResult;
    };
    }

    /**
     * Handles emergency transport change by setting new emergency mode.
@@ -480,6 +478,36 @@ public class EmergencyStateTracker {
        setEmergencyMode(mode, MSG_SET_EMERGENCY_MODE_DONE);
    }

    /**
     * Notify the tracker that the emergency call domain has been updated.
     * @param phoneType The new PHONE_TYPE_* of the call.
     * @param callId The ID of the call
     */
    public void onEmergencyCallDomainUpdated(int phoneType, String callId) {
        Rlog.d(TAG, "domain update for callId: " + callId);
        int domain = -1;
        switch(phoneType) {
            case (PhoneConstants.PHONE_TYPE_CDMA_LTE):
                //fallthrough
            case (PhoneConstants.PHONE_TYPE_GSM):
                //fallthrough
            case (PhoneConstants.PHONE_TYPE_CDMA): {
                domain = NetworkRegistrationInfo.DOMAIN_CS;
                break;
            }
            case (PhoneConstants.PHONE_TYPE_IMS): {
                domain = NetworkRegistrationInfo.DOMAIN_PS;
                break;
            }
            default: {
                Rlog.w(TAG, "domain updated: Unexpected phoneType:" + phoneType);
            }
        }
        if (mEmergencyCallDomain == domain) return;
        Rlog.i(TAG, "domain updated: from " + mEmergencyCallDomain + " to " + domain);
        mEmergencyCallDomain = domain;
    }

    /**
     * Handles emergency call state change.
     *
@@ -496,23 +524,16 @@ public class EmergencyStateTracker {
     * Returns {@code true} if device and carrier support emergency callback mode.
     */
    private boolean emergencyCallbackModeSupported() {
        // Check Carrier Configs if ECM is enabled.
        CarrierConfigManager cfgManager = (CarrierConfigManager) mPhone.getContext()
                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
        if (cfgManager == null) {
            // If CarrierConfigManager is unavailable, return default value for ECM.
            Rlog.w(TAG, "emergencyCallbackModeSupported: couldn't get CarrierConfigManager");
            return DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED;
        }

        return cfgManager.getConfigForSubId(mPhone.getSubId()).getBoolean(
                CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL);
        return getConfig(mPhone.getSubId(),
                CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
                DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED);
    }

    /**
     * Trigger entry into emergency callback mode.
     */
    private void enterEmergencyCallbackMode() {
        Rlog.d(TAG, "enter ECBM");
        setIsInEmergencyCall(false);
        // Check if not in ECM already.
        if (!isInEcm()) {
@@ -523,7 +544,9 @@ public class EmergencyStateTracker {

            // Notify listeners of the entrance to ECM.
            sendEmergencyCallbackModeChange();
            if (mPhone.getImsPhone() != null) {
            if (isInImsEcm()) {
                // emergency call registrants are not notified of new emergency call until entering
                // ECBM (see ImsPhone#handleEnterEmergencyCallbackMode)
                ((GsmCdmaPhone) mPhone).notifyEmergencyCallRegistrants(true);
            }

@@ -533,11 +556,11 @@ public class EmergencyStateTracker {
            // Post this runnable so we will automatically exit if no one invokes
            // exitEmergencyCallbackMode() directly.
            long delayInMillis = TelephonyProperties.ecm_exit_timer()
                    .orElse(defaultEcmExitTimeoutMs);
                    .orElse(mEcmExitTimeoutMs);
            mHandler.postDelayed(mExitEcmRunnable, delayInMillis);

            // We don't want to go to sleep while in ECM.
            mWakeLock.acquire();
            if (mWakeLock != null) mWakeLock.acquire(mEcmExitTimeoutMs);
        }
    }

@@ -545,8 +568,11 @@ public class EmergencyStateTracker {
     * Exits emergency callback mode and notifies relevant listeners.
     */
    public void exitEmergencyCallbackMode() {
        Rlog.d(TAG, "ecit ECBM");
        // Remove pending exit ECM runnable, if any.
        mHandler.removeCallbacks(mExitEcmRunnable);
        mEmergencyCallDomain = -1;
        mIsTestEmergencyNumber = false;

        if (isInEcm()) {
            setIsInEcm(false);
@@ -555,7 +581,7 @@ public class EmergencyStateTracker {
            }

            // Release wakeLock.
            if (mWakeLock.isHeld()) {
            if (mWakeLock != null && mWakeLock.isHeld()) {
                mWakeLock.release();
            }

@@ -607,17 +633,21 @@ public class EmergencyStateTracker {
    }

    /**
     * Returns {@code true} if currently in emergency callback mode with an ImsPhone.
     * Returns {@code true} if currently in emergency callback mode over PS
     */
    public boolean isInImsEcm() {
        return mPhone.getImsPhone() != null && isInEcm();
        return mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS && isInEcm();
    }

    /**
     * Returns {@code true} if currently in emergency callback mode with an ImsPhone.
     * Returns {@code true} if currently in emergency callback mode over CS
     */
    public boolean isInCdmaEcm() {
        return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA && isInEcm();
        // Phone can be null in the case where we are not actively tracking an emergency call.
        if (mPhone == null) return false;
        // Ensure that this method doesn't return true when we are attached to GSM.
        return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
                && mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_CS && isInEcm();
    }

    /**
@@ -699,15 +729,6 @@ public class EmergencyStateTracker {
            return CompletableFuture.completedFuture(Boolean.TRUE);
        }

        CarrierConfigManager cfgManager = (CarrierConfigManager) phone.getContext()
                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
        if (cfgManager == null) {
            // For some reason CarrierConfigManager is unavailable. Do not block emergency call.
            Rlog.w(TAG, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get"
                    + "CarrierConfigManager");
            return CompletableFuture.completedFuture(Boolean.TRUE);
        }

        // Only override default data if we are IN_SERVICE already.
        if (!isAvailableForEmergencyCalls(phone)) {
            Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS");
@@ -721,7 +742,7 @@ public class EmergencyStateTracker {
        // fallback even though the home operator does. For these operators we will need to do a DDS
        // switch anyway to make sure the SUPL request doesn't fail.
        boolean roamingNetworkSupportsControlPlaneFallback = true;
        String[] dataPlaneRoamPlmns = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
        String[] dataPlaneRoamPlmns = getConfig(phone.getSubId(),
                CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY);
        if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns)
                .contains(phone.getServiceState().getOperatorNumeric())) {
@@ -735,7 +756,7 @@ public class EmergencyStateTracker {
        // Do not try to swap default data if we support CS fallback or it is assumed that the
        // roaming network supports control plane fallback, we do not want to introduce a lag in
        // emergency call setup time if possible.
        final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId()).getInt(
        final boolean supportsCpFallback = getConfig(phone.getSubId(),
                CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
                != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
@@ -749,8 +770,8 @@ public class EmergencyStateTracker {
        // CarrierConfig default if format fails.
        int extensionTime = 0;
        try {
            extensionTime = Integer.parseInt(cfgManager.getConfigForSubId(phone.getSubId())
                    .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
            extensionTime = Integer.parseInt(getConfig(phone.getSubId(),
                    CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
        } catch (NumberFormatException e) {
            // Just use default.
        }
@@ -769,6 +790,24 @@ public class EmergencyStateTracker {
        return modemResultFuture;
    }

    // Helper functions for easy CarrierConfigManager access
    private String getConfig(int subId, String key, String defVal) {
        return getConfigBundle(subId, key).getString(key, defVal);
    }
    private int getConfig(int subId, String key, int defVal) {
        return getConfigBundle(subId, key).getInt(key, defVal);
    }
    private String[] getConfig(int subId, String key) {
        return getConfigBundle(subId, key).getStringArray(key);
    }
    private boolean getConfig(int subId, String key, boolean defVal) {
        return getConfigBundle(subId, key).getBoolean(key, defVal);
    }
    private PersistableBundle getConfigBundle(int subId, String key) {
        if (mConfigManager == null) return new PersistableBundle();
        return mConfigManager.getConfigForSubId(subId, key);
    }

    /**
     * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
     */
+1 −0
Original line number Diff line number Diff line
@@ -778,6 +778,7 @@ public class ContextFixture implements TestFixture<Context> {

        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
        doReturn(mBundle).when(mCarrierConfigManager).getConfig();
        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
        doAnswer(invocation -> mNetworkId++).when(mNetwork).getNetId();
        doReturn(mNetwork).when(mConnectivityManager).registerNetworkAgent(
                any(), any(), any(), any(), any(), any(), anyInt());
+110 −12

File changed.

Preview size limit exceeded, changes collapsed.