Merge "Improved locale tracker logging"
diff --git a/Android.bp b/Android.bp
index 9f5459b..80070b7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,6 +47,7 @@
     ],
     static_libs: [
         "telephony-protos",
+        "ecc-protos-lite",
         "android.hardware.radio-V1.0-java",
         "android.hardware.radio-V1.1-java",
         "android.hardware.radio-V1.2-java",
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index 6fa46df..d49c2b2 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -1556,4 +1556,20 @@
 
   // Amount of time modem is in tx (ms)
   repeated int64 tx_time_ms = 9;
+
+  // Number of bytes sent (tx)
+  optional int64 num_bytes_tx = 10;
+
+  // Number of packets received (rx)
+  optional int64 num_packets_rx = 11;
+
+  // Number of bytes received (rx)
+  optional int64 num_bytes_rx = 12;
+
+  // Amount of time phone spends in various Radio Access Technologies (ms)
+  repeated int64 time_in_rat_ms = 13;
+
+  // Amount of time phone spends in various cellular
+  // rx signal strength levels (ms)
+  repeated int64 time_in_rx_signal_strength_level_ms = 14;
 }
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index d5e7e0c..2695951 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -78,6 +78,7 @@
     protected RegistrantList mNattKeepaliveStatusRegistrants = new RegistrantList();
     protected RegistrantList mPhysicalChannelConfigurationRegistrants = new RegistrantList();
     protected RegistrantList mLceInfoRegistrants = new RegistrantList();
+    protected RegistrantList mEmergencyNumberListRegistrants = new RegistrantList();
 
     protected Registrant mGsmSmsRegistrant;
     protected Registrant mCdmaSmsRegistrant;
@@ -783,6 +784,17 @@
         mSubscriptionStatusRegistrants.remove(h);
     }
 
+    @Override
+    public void registerForEmergencyNumberList(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mEmergencyNumberListRegistrants.add(r);
+    }
+
+    @Override
+    public void unregisterForEmergencyNumberList(Handler h) {
+        mEmergencyNumberListRegistrants.remove(h);
+    }
+
     //***** Protected Methods
     /**
      * Store new RadioState and send notification based on the changes
diff --git a/src/java/com/android/internal/telephony/CallFailCause.java b/src/java/com/android/internal/telephony/CallFailCause.java
index acc1432..6e4e01c 100644
--- a/src/java/com/android/internal/telephony/CallFailCause.java
+++ b/src/java/com/android/internal/telephony/CallFailCause.java
@@ -149,6 +149,9 @@
     int DIAL_MODIFIED_TO_SS   = 245;
     int DIAL_MODIFIED_TO_DIAL = 246;
 
+    //Access class blocked - TS 31.121 5.2.1
+    int ACCESS_CLASS_BLOCKED = 260;
+
     //Emergency Redial
     int EMERGENCY_TEMP_FAILURE = 325;
     int EMERGENCY_PERM_FAILURE = 326;
diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java
index fa84b20..edbbcda 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -276,7 +276,7 @@
                 return new NetworkRegistrationState(domain, transportType, regState,
                         accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
                         cellIdentity, maxDataCalls, false /* isDcNrRestricted */,
-                        false /* isNrAvailable */);
+                        false /* isNrAvailable */, false /* isEnDcAvailable */);
 
             } else if (result instanceof android.hardware.radio.V1_2.DataRegStateResult) {
                 android.hardware.radio.V1_2.DataRegStateResult dataRegState =
@@ -293,7 +293,25 @@
                 return new NetworkRegistrationState(domain, transportType, regState,
                         accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
                         cellIdentity, maxDataCalls, false /* isDcNrRestricted */,
-                        false /* isNrAvailable */);
+                        false /* isNrAvailable */, false /* isEnDcAvailable */);
+            } else if (result instanceof android.hardware.radio.V1_4.DataRegStateResult) {
+                android.hardware.radio.V1_4.DataRegStateResult dataRegState =
+                        (android.hardware.radio.V1_4.DataRegStateResult) result;
+                int regState = getRegStateFromHalRegState(dataRegState.base.regState);
+                int accessNetworkTechnology =
+                        getAccessNetworkTechnologyFromRat(dataRegState.base.rat);
+                int reasonForDenial = dataRegState.base.reasonDataDenied;
+                boolean emergencyOnly = isEmergencyOnly(dataRegState.base.regState);
+                int maxDataCalls = dataRegState.base.maxDataCalls;
+                int[] availableServices = getAvailableServices(regState, domain, emergencyOnly);
+                CellIdentity cellIdentity =
+                        convertHalCellIdentityToCellIdentity(dataRegState.base.cellIdentity);
+                android.hardware.radio.V1_4.NrIndicators nrIndicators = dataRegState.nrIndicators;
+
+                return new NetworkRegistrationState(domain, transportType, regState,
+                        accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
+                        cellIdentity, maxDataCalls, nrIndicators.isDcNrRestricted,
+                        nrIndicators.isNrAvailable, nrIndicators.isEndcAvailable);
             }
             return null;
         }
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 8b39e8b..cda5cd7 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -2221,6 +2221,22 @@
     void unregisterForNattKeepaliveStatus(Handler h);
 
     /**
+     * Register for unsolicited Emergency Number List Indications
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    void registerForEmergencyNumberList(Handler h, int what, Object obj);
+
+    /**
+     * Deregister for unsolicited Emergency Number List Indications
+     *
+     * @param h Handler for notification message.
+     */
+    void unregisterForEmergencyNumberList(Handler h);
+
+    /**
      * Start sending NATT Keepalive packets on a specified data connection
      *
      * @param contextId cid that identifies the data connection for this keepalive
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index c4701f7..ee2a9bb 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -31,6 +31,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.emergency.EmergencyNumber;
 
 import java.util.List;
 
@@ -362,6 +363,19 @@
         }
     }
 
+    @Override
+    public void notifyEmergencyNumberList(Phone sender,
+                                          List<EmergencyNumber> emergencyNumberList) {
+        int subId = sender.getSubId();
+        try {
+            if (mRegistry != null) {
+                mRegistry.notifyEmergencyNumberList(emergencyNumberList);
+            }
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
     /**
      * Convert the {@link Phone.DataActivityState} enum into the TelephonyManager.DATA_* constants
      * for the public API.
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 3a96630..358f15f 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -27,6 +27,9 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.radio.V1_2.IndicationFilter;
 import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Message;
@@ -39,11 +42,14 @@
 import android.util.SparseIntArray;
 import android.view.Display;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * The device state monitor monitors the device state such as charging state, power saving sate,
@@ -58,21 +64,65 @@
     protected static final boolean DBG = false;      /* STOPSHIP if true */
     protected static final String TAG = DeviceStateMonitor.class.getSimpleName();
 
-    private static final int EVENT_RIL_CONNECTED                = 0;
-    private static final int EVENT_UPDATE_MODE_CHANGED          = 1;
-    private static final int EVENT_SCREEN_STATE_CHANGED         = 2;
-    private static final int EVENT_POWER_SAVE_MODE_CHANGED      = 3;
-    private static final int EVENT_CHARGING_STATE_CHANGED       = 4;
-    private static final int EVENT_TETHERING_STATE_CHANGED      = 5;
-    private static final int EVENT_RADIO_AVAILABLE              = 6;
+    static final int EVENT_RIL_CONNECTED                = 0;
+    static final int EVENT_UPDATE_MODE_CHANGED          = 1;
+    @VisibleForTesting
+    static final int EVENT_SCREEN_STATE_CHANGED         = 2;
+    static final int EVENT_POWER_SAVE_MODE_CHANGED      = 3;
+    @VisibleForTesting
+    static final int EVENT_CHARGING_STATE_CHANGED       = 4;
+    static final int EVENT_TETHERING_STATE_CHANGED      = 5;
+    static final int EVENT_RADIO_AVAILABLE              = 6;
+    @VisibleForTesting
+    static final int EVENT_WIFI_CONNECTION_CHANGED      = 7;
 
     // TODO(b/74006656) load hysteresis values from a property when DeviceStateMonitor starts
     private static final int HYSTERESIS_KBPS = 50;
 
+    private static final int WIFI_UNAVAILABLE = 0;
+    private static final int WIFI_AVAILABLE = 1;
+
     private final Phone mPhone;
 
     private final LocalLog mLocalLog = new LocalLog(100);
 
+    private final NetworkRequest mWifiNetworkRequest =
+            new NetworkRequest.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+            .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+            .build();
+
+    private final ConnectivityManager.NetworkCallback mNetworkCallback =
+            new ConnectivityManager.NetworkCallback() {
+        Set<Network> mWifiNetworks = new HashSet<>();
+
+        @Override
+        public void onAvailable(Network network) {
+            synchronized (mWifiNetworks) {
+                if (mWifiNetworks.size() == 0) {
+                    // We just connected to Wifi, so send an update.
+                    obtainMessage(EVENT_WIFI_CONNECTION_CHANGED, WIFI_AVAILABLE, 0).sendToTarget();
+                    log("Wifi (default) connected", true);
+                }
+                mWifiNetworks.add(network);
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            synchronized (mWifiNetworks) {
+                mWifiNetworks.remove(network);
+                if (mWifiNetworks.size() == 0) {
+                    // We just disconnected from the last connected wifi, so send an update.
+                    obtainMessage(
+                            EVENT_WIFI_CONNECTION_CHANGED, WIFI_UNAVAILABLE, 0).sendToTarget();
+                    log("Wifi (default) disconnected", true);
+                }
+            }
+        }
+    };
+
     /**
      * Flag for wifi/usb/bluetooth tethering turned on or not
      */
@@ -105,6 +155,22 @@
      */
     private boolean mIsLowDataExpected;
 
+    /**
+     * Wifi is connected. True means both that cellular is likely to be asleep when the screen is
+     * on and that in most cases the device location is relatively close to the WiFi AP. This means
+     * that fewer location updates should be provided by cellular.
+     */
+    private boolean mIsWifiConnected;
+
+    @VisibleForTesting
+    static final int CELL_INFO_INTERVAL_SHORT_MS = 2000;
+    @VisibleForTesting
+    static final int CELL_INFO_INTERVAL_LONG_MS = 10000;
+
+    /** The minimum required wait time between cell info requests to the modem */
+    private int mCellInfoMinInterval = CELL_INFO_INTERVAL_SHORT_MS;
+
+
     private SparseIntArray mUpdateModes = new SparseIntArray();
 
     /**
@@ -202,6 +268,10 @@
 
         mPhone.mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
         mPhone.mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+
+        ConnectivityManager cm = (ConnectivityManager) phone.getContext().getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        cm.registerNetworkCallback(mWifiNetworkRequest, mNetworkCallback);
     }
 
     /**
@@ -212,6 +282,27 @@
     }
 
     /**
+     * @return The minimum period between CellInfo requests to the modem
+     */
+    @VisibleForTesting
+    public int computeCellInfoMinInterval() {
+        // The screen is on and we're either on cellular or charging. Screen on + Charging is
+        // a likely vehicular scenario, even if there is a nomadic AP.
+        if (mIsScreenOn && !mIsWifiConnected) {
+            // Screen on without WiFi - We are in a high power likely mobile situation.
+            return CELL_INFO_INTERVAL_SHORT_MS;
+        } else if (mIsScreenOn && mIsCharging) {
+            // Screen is on and we're charging, so we favor accuracy over power.
+            return CELL_INFO_INTERVAL_SHORT_MS;
+        } else {
+            // If the screen is off, apps should not need cellular location at rapid intervals.
+            // If the screen is on but we are on wifi and not charging then cellular location
+            // accuracy is not crucial, so favor modem power saving over high accuracy.
+            return CELL_INFO_INTERVAL_LONG_MS;
+        }
+    }
+
+    /**
      * @return True if signal strength update should be turned off.
      */
     private boolean shouldTurnOffSignalStrength() {
@@ -358,6 +449,9 @@
             case EVENT_TETHERING_STATE_CHANGED:
                 onUpdateDeviceState(msg.what, msg.arg1 != 0);
                 break;
+            case EVENT_WIFI_CONNECTION_CHANGED:
+                onUpdateDeviceState(msg.what, msg.arg1 != WIFI_UNAVAILABLE);
+                break;
             default:
                 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
         }
@@ -389,10 +483,22 @@
                 mIsPowerSaveOn = state;
                 sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn);
                 break;
+            case EVENT_WIFI_CONNECTION_CHANGED:
+                if (mIsWifiConnected == state) return;
+                mIsWifiConnected = state;
+
+                break;
             default:
                 return;
         }
 
+        final int newCellInfoMinInterval = computeCellInfoMinInterval();
+        if (mCellInfoMinInterval != newCellInfoMinInterval) {
+            mCellInfoMinInterval = newCellInfoMinInterval;
+            setCellInfoMinInterval(mCellInfoMinInterval);
+            log("CellInfo Min Interval Updated to " + newCellInfoMinInterval, true);
+        }
+
         if (mIsLowDataExpected != isLowDataExpected()) {
             mIsLowDataExpected = !mIsLowDataExpected;
             sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected);
@@ -437,6 +543,7 @@
         setUnsolResponseFilter(mUnsolicitedResponseFilter, true);
         setSignalStrengthReportingCriteria();
         setLinkCapacityReportingCriteria();
+        setCellInfoMinInterval(mCellInfoMinInterval);
     }
 
     /**
@@ -501,6 +608,10 @@
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.CDMA2000);
     }
 
+    private void setCellInfoMinInterval(int rate) {
+        mPhone.setCellInfoMinInterval(rate);
+    }
+
     /**
      * @return True if the device is currently in power save mode.
      * See {@link android.os.BatteryManager#isPowerSaveMode BatteryManager.isPowerSaveMode()}.
@@ -579,6 +690,7 @@
         ipw.println("mIsPowerSaveOn=" + mIsPowerSaveOn);
         ipw.println("mIsLowDataExpected=" + mIsLowDataExpected);
         ipw.println("mUnsolicitedResponseFilter=" + mUnsolicitedResponseFilter);
+        ipw.println("mIsWifiConnected=" + mIsWifiConnected);
         ipw.println("Local logs:");
         ipw.increaseIndent();
         mLocalLog.dump(fd, ipw, args);
diff --git a/src/java/com/android/internal/telephony/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index ae1155b..84c9e0a 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -528,6 +528,7 @@
             case CallFailCause.USER_ALERTING_NO_ANSWER:
                 return DisconnectCause.TIMED_OUT;
 
+            case CallFailCause.ACCESS_CLASS_BLOCKED:
             case CallFailCause.ERROR_UNSPECIFIED:
             case CallFailCause.NORMAL_CLEARING:
             default:
@@ -562,7 +563,8 @@
                     }
                 }
                 if (isPhoneTypeGsm()) {
-                    if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
+                    if (causeCode == CallFailCause.ERROR_UNSPECIFIED ||
+                                   causeCode == CallFailCause.ACCESS_CLASS_BLOCKED ) {
                         if (phone.mSST.mRestrictedState.isCsRestricted()) {
                             return DisconnectCause.CS_RESTRICTED;
                         } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) {
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index c98d9b0..4ce5a8c 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -77,7 +77,9 @@
 import com.android.internal.telephony.cdma.CdmaMmiCode;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
+import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.test.SimulatedRadioControl;
@@ -169,6 +171,7 @@
     private IsimUiccRecords mIsimUiccRecords;
     public GsmCdmaCallTracker mCT;
     public ServiceStateTracker mSST;
+    public EmergencyNumberTracker mEmergencyNumberTracker;
     private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>();
     private IccPhoneBookInterfaceManager mIccPhoneBookIntManager;
     // Used for identify the carrier of current subscription
@@ -224,20 +227,29 @@
         initRatSpecific(precisePhoneType);
         // CarrierSignalAgent uses CarrierActionAgent in construction so it needs to be created
         // after CarrierActionAgent.
-        mCarrierActionAgent = mTelephonyComponentFactory.makeCarrierActionAgent(this);
-        mCarrierSignalAgent = mTelephonyComponentFactory.makeCarrierSignalAgent(this);
-        mTransportManager = mTelephonyComponentFactory.makeTransportManager(this);
-        mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi);
+        mCarrierActionAgent = mTelephonyComponentFactory.inject(CarrierActionAgent.class.getName())
+                .makeCarrierActionAgent(this);
+        mCarrierSignalAgent = mTelephonyComponentFactory.inject(CarrierSignalAgent.class.getName())
+                .makeCarrierSignalAgent(this);
+        mTransportManager = mTelephonyComponentFactory.inject(TransportManager.class.getName())
+                .makeTransportManager(this);
+        mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
+                .makeServiceStateTracker(this, this.mCi);
+        mEmergencyNumberTracker = mTelephonyComponentFactory
+                .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
+                this, this.mCi);
         // DcTracker uses SST so needs to be created after it is instantiated
         for (int transport : mTransportManager.getAvailableTransports()) {
-            mDcTrackers.put(transport, mTelephonyComponentFactory.makeDcTracker(this,
-                    transport));
+            mDcTrackers.put(transport, mTelephonyComponentFactory.inject(DcTracker.class.getName())
+                    .makeDcTracker(this, transport));
         }
 
-        mCarrierResolver = mTelephonyComponentFactory.makeCarrierResolver(this);
+        mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
+                .makeCarrierResolver(this);
 
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
-        mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this);
+        mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
+                .makeDeviceStateMonitor(this);
 
         mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
         logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
@@ -258,12 +270,17 @@
             mSimulatedRadioControl = (SimulatedRadioControl) ci;
         }
 
-        mCT = mTelephonyComponentFactory.makeGsmCdmaCallTracker(this);
-        mIccPhoneBookIntManager = mTelephonyComponentFactory.makeIccPhoneBookInterfaceManager(this);
+        mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
+                .makeGsmCdmaCallTracker(this);
+        mIccPhoneBookIntManager = mTelephonyComponentFactory
+                .inject(IccPhoneBookInterfaceManager.class.getName())
+                .makeIccPhoneBookInterfaceManager(this);
         PowerManager pm
                 = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
-        mIccSmsInterfaceManager = mTelephonyComponentFactory.makeIccSmsInterfaceManager(this);
+        mIccSmsInterfaceManager = mTelephonyComponentFactory
+                .inject(IccSmsInterfaceManager.class.getName())
+                .makeIccSmsInterfaceManager(this);
 
         mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
         mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
@@ -276,10 +293,11 @@
         mCi.setOnSs(this, EVENT_SS, null);
 
         //CDMA
-        mCdmaSSM = mTelephonyComponentFactory.getCdmaSubscriptionSourceManagerInstance(mContext,
+        mCdmaSSM = mTelephonyComponentFactory.inject(CdmaSubscriptionSourceManager.class.getName())
+                .getCdmaSubscriptionSourceManagerInstance(mContext,
                 mCi, this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
-        mEriManager = mTelephonyComponentFactory.makeEriManager(this, mContext,
-                EriManager.ERI_FROM_XML);
+        mEriManager = mTelephonyComponentFactory.inject(EriManager.class.getName())
+                .makeEriManager(this, mContext, EriManager.ERI_FROM_XML);
         mCi.setEmergencyCallbackMode(this, EVENT_EMERGENCY_CALLBACK_MODE_ENTER, null);
         mCi.registerForExitEmergencyCallbackMode(this, EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE,
                 null);
@@ -476,6 +494,11 @@
     }
 
     @Override
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        return mEmergencyNumberTracker;
+    }
+
+    @Override
     public CallTracker getCallTracker() {
         return mCT;
     }
@@ -1869,9 +1892,7 @@
             int serviceClass) {
         if (isPhoneTypeGsm()) {
             Phone imsPhone = mImsPhone;
-            if ((imsPhone != null)
-                    && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
-                    || imsPhone.isUtEnabled())) {
+            if ((imsPhone != null) && imsPhone.isUtEnabled()) {
                 imsPhone.getCallBarring(facility, password, onComplete, serviceClass);
                 return;
             }
@@ -1886,9 +1907,7 @@
             Message onComplete, int serviceClass) {
         if (isPhoneTypeGsm()) {
             Phone imsPhone = mImsPhone;
-            if ((imsPhone != null)
-                    && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
-                    || imsPhone.isUtEnabled())) {
+            if ((imsPhone != null) && imsPhone.isUtEnabled()) {
                 imsPhone.setCallBarring(facility, lockState, password, onComplete, serviceClass);
                 return;
             }
diff --git a/src/java/com/android/internal/telephony/IccCard.java b/src/java/com/android/internal/telephony/IccCard.java
index 7bab408..75f3377 100644
--- a/src/java/com/android/internal/telephony/IccCard.java
+++ b/src/java/com/android/internal/telephony/IccCard.java
@@ -275,6 +275,14 @@
         return false;
     }
 
+    /**
+     * @return whether the card is an empty profile, meaning there's no UiccCardApplication,
+     * and that we don't need to wait for LOADED state.
+     */
+    public boolean isEmptyProfile() {
+        return false;
+    }
+
     private void sendMessageWithCardAbsentException(Message onComplete) {
         AsyncResult ret = AsyncResult.forMessage(onComplete);
         ret.exception = new RuntimeException("No valid IccCard");
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index a115dba..5124748 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -259,7 +259,8 @@
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
         mWakeLock.acquire();    // wake lock released after we enter idle state
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mDeviceIdleController = TelephonyComponentFactory.getInstance().getIDeviceIdleController();
+        mDeviceIdleController = TelephonyComponentFactory.getInstance()
+                .inject(IDeviceIdleController.class.getName()).getIDeviceIdleController();
 
         addState(mDefaultState);
         addState(mStartupState, mDefaultState);
@@ -719,8 +720,9 @@
                 destPort = smsHeader.portAddrs.destPort;
                 if (DBG) log("destination port: " + destPort);
             }
-
-            tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(sms.getPdu(),
+            tracker = TelephonyComponentFactory.getInstance()
+                    .inject(InboundSmsTracker.class.getName())
+                    .makeInboundSmsTracker(sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), false,
                     sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(),
                     sms.getMessageBody());
@@ -729,8 +731,9 @@
             SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
             SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
             int destPort = (portAddrs != null ? portAddrs.destPort : -1);
-
-            tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(sms.getPdu(),
+            tracker = TelephonyComponentFactory.getInstance()
+                    .inject(InboundSmsTracker.class.getName())
+                    .makeInboundSmsTracker(sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), sms.getOriginatingAddress(),
                     sms.getDisplayOriginatingAddress(), concatRef.refNumber, concatRef.seqNumber,
                     concatRef.msgCount, false, sms.getMessageBody());
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index c4f203e..e6ec174 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -57,6 +57,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.ApnSetting.ApnType;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.SparseArray;
@@ -68,6 +69,7 @@
 import com.android.internal.telephony.dataconnection.DataConnectionReasons;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
@@ -483,7 +485,8 @@
         mCi = ci;
         mActionDetached = this.getClass().getPackage().getName() + ".action_detached";
         mActionAttached = this.getClass().getPackage().getName() + ".action_attached";
-        mAppSmsManager = telephonyComponentFactory.makeAppSmsManager(context);
+        mAppSmsManager = telephonyComponentFactory.inject(AppSmsManager.class.getName())
+                .makeAppSmsManager(context);
 
         if (Build.IS_DEBUGGABLE) {
             mTelephonyTester = new TelephonyTester(this);
@@ -545,11 +548,15 @@
 
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mTelephonyComponentFactory = telephonyComponentFactory;
-        mSmsStorageMonitor = mTelephonyComponentFactory.makeSmsStorageMonitor(this);
-        mSmsUsageMonitor = mTelephonyComponentFactory.makeSmsUsageMonitor(context);
+        mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
+                .makeSmsStorageMonitor(this);
+        mSmsUsageMonitor = mTelephonyComponentFactory.inject(SmsUsageMonitor.class.getName())
+                .makeSmsUsageMonitor(context);
         mUiccController = UiccController.getInstance();
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
-        mSimActivationTracker = mTelephonyComponentFactory.makeSimActivationTracker(this);
+        mSimActivationTracker = mTelephonyComponentFactory
+                .inject(SimActivationTracker.class.getName())
+                .makeSimActivationTracker(this);
         if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
             mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
         }
@@ -1629,6 +1636,13 @@
     }
 
     /**
+     * Retrieves the EmergencyNumberTracker of the phone instance.
+     */
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        return null;
+    }
+
+    /**
     * Get call tracker
     */
     public CallTracker getCallTracker() {
@@ -1722,6 +1736,11 @@
         return (r != null) ? r.getRecordsLoaded() : false;
     }
 
+    /** Set the minimum interval for CellInfo requests to the modem */
+    public void setCellInfoMinInterval(int interval) {
+        getServiceStateTracker().setCellInfoMinInterval(interval);
+    }
+
     /**
      * @return the last known CellInfo
      */
@@ -2241,6 +2260,11 @@
         mNotifier.notifySrvccStateChanged(this, state);
     }
 
+    /** Notify the list of {@link EmergencyNumber} changes. */
+    public void notifyEmergencyNumberList(List<EmergencyNumber> emergencyNumberList) {
+        mNotifier.notifyEmergencyNumberList(this, emergencyNumberList);
+    }
+
     /**
      * @return true if a mobile originating emergency call is active
      */
@@ -3917,6 +3941,17 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        if (getEmergencyNumberTracker() != null) {
+            try {
+                getEmergencyNumberTracker().dump(fd, pw, args);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            pw.flush();
+            pw.println("++++++++++++++++++++++++++++++++");
+        }
+
         if (mCarrierActionAgent != null) {
             try {
                 mCarrierActionAgent.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 0568187..30b53d7 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -21,6 +21,7 @@
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 
 import java.util.List;
 
@@ -78,4 +79,7 @@
     public void notifyPhoneCapabilityChanged(PhoneCapability capability);
 
     void notifyRadioPowerStateChanged(@TelephonyManager.RadioPowerState int state);
+
+    /** Notify of change to EmergencyNumberList. */
+    void notifyEmergencyNumberList(Phone sender, List<EmergencyNumber> emergencyNumberList);
 }
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 9181e98..eb10904 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -3138,10 +3138,6 @@
         }
     }
 
-    void setCellInfoListRate() {
-        setCellInfoListRate(Integer.MAX_VALUE, null, mRILDefaultWorkSource);
-    }
-
     @Override
     public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) {
 
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index f2214d6..1614a27 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -672,7 +672,6 @@
         // Initial conditions
         mRil.setRadioPower(false, null);
         mRil.setCdmaSubscriptionSource(mRil.mCdmaSubscription, null);
-        mRil.setCellInfoListRate();
         // todo: this should not require a version number now. Setting it to latest RIL version for
         // now.
         mRil.notifyRegistrantsRilConnectionChanged(15);
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 4d91a27..cd1f44a 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -128,13 +128,13 @@
     private ServiceState mNewSS;
 
     // This is the minimum interval at which CellInfo requests will be serviced by the modem.
-    // Any requests that arrive within MAX_AGE of the previous reuqest will simply receive the
+    // Any requests that arrive within MinInterval of the previous reuqest will simply receive the
     // cached result. This is a power-saving feature, because requests to the modem may require
     // wakeup of a separate chip and bus communication. Because the cost of wakeups is
     // architecture dependent, it would be preferable if this sort of optimization could be
     // handled in SoC-specific code, but for now, keep it here to ensure that in case further
     // optimizations are not present elsewhere, there is a power-management scheme of last resort.
-    private static final long LAST_CELL_INFO_LIST_MAX_AGE_MS = 2000;
+    private int mCellInfoMinIntervalMs =  2000;
 
     // Maximum time to wait for a CellInfo request before assuming it won't arrive and returning
     // null to callers. Note, that if a CellInfo response does arrive later, then it will be
@@ -494,7 +494,9 @@
     private static final int INVALID_LTE_EARFCN = -1;
 
     public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
-        mNitzState = TelephonyComponentFactory.getInstance().makeNitzStateMachine(phone);
+        mNitzState = TelephonyComponentFactory.getInstance()
+                .inject(NitzStateMachine.class.getName())
+                .makeNitzStateMachine(phone);
         mPhone = phone;
         mCi = ci;
 
@@ -522,9 +524,9 @@
             mRegStateManagers.get(transportType).registerForNetworkRegistrationStateChanged(
                     this, EVENT_NETWORK_STATE_CHANGED, null);
         }
-
-        mLocaleTracker = TelephonyComponentFactory.getInstance().makeLocaleTracker(
-                mPhone, mNitzState, getLooper());
+        mLocaleTracker = TelephonyComponentFactory.getInstance()
+                .inject(LocaleTracker.class.getName())
+                .makeLocaleTracker(mPhone, mNitzState, getLooper());
 
         mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
@@ -4354,6 +4356,11 @@
         return mLastCellInfoList;
     }
 
+    /** Set the minimum time between CellInfo requests to the modem, in milliseconds */
+    public void setCellInfoMinInterval(int interval) {
+        mCellInfoMinIntervalMs = interval;
+    }
+
     /**
      * Request the latest CellInfo from the modem.
      *
@@ -4381,7 +4388,7 @@
             // Check to see whether the elapsed time is sufficient for a new request; if not, then
             // return the result of the last request (if expected).
             final long curTime = SystemClock.elapsedRealtime();
-            if ((curTime - mLastCellInfoReqTime) < LAST_CELL_INFO_LIST_MAX_AGE_MS) {
+            if ((curTime - mLastCellInfoReqTime) < mCellInfoMinIntervalMs) {
                 if (rspMsg != null) {
                     if (DBG) log("SST.requestAllCellInfo(): return last, back to back calls");
                     AsyncResult.forMessage(rspMsg, mLastCellInfoList, null);
@@ -4563,6 +4570,7 @@
         pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
         pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
         pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
+        pw.println(" mCellInfoMinIntervalMs=" + mCellInfoMinIntervalMs);
         dumpEarfcnPairList(pw);
 
         mLocaleTracker.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 32edb9c..516ca3f 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -165,7 +165,8 @@
             while (cursor.moveToNext()) {
                 InboundSmsTracker tracker;
                 try {
-                    tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(cursor,
+                    tracker = TelephonyComponentFactory.getInstance()
+                            .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(cursor,
                             isCurrentFormat3gpp2);
                 } catch (IllegalArgumentException e) {
                     Rlog.e(TAG, "error loading SmsTracker: " + e);
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 09fc531..09fcc93 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -21,6 +21,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -615,7 +616,7 @@
             return;
         }
 
-        boolean opptSubListChanged = false;
+        boolean opptSubListChanged;
 
         synchronized (mSubInfoListLock) {
             mCacheActiveSubInfoList.clear();
@@ -2450,14 +2451,31 @@
         }
     }
 
-
     private boolean refreshCachedOpportunisticSubscriptionInfoList() {
         synchronized (mSubInfoListLock) {
             List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;
 
-            mCacheOpportunisticSubInfoList = mCacheActiveSubInfoList.stream()
-                    .filter(subscriptionInfo -> subscriptionInfo.isOpportunistic())
-                    .collect(Collectors.toList());
+            List<SubscriptionInfo> subList = getSubInfo(
+                    SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND ("
+                            + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
+                            + SubscriptionManager.IS_EMBEDDED + "=1)", null);
+
+            if (subList != null) {
+                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
+            } else {
+                subList = new ArrayList<>();
+            }
+
+            mCacheOpportunisticSubInfoList = subList;
+
+            for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
+                if (shouldDisableSubGroup(info.getGroupUuid())) {
+                    info.setGroupDisabled(true);
+                    if (isActiveSubId(info.getSubscriptionId())) {
+                        deactivateSubscription(info);
+                    }
+                }
+            }
 
             if (DBG_CACHE) {
                 if (!mCacheOpportunisticSubInfoList.isEmpty()) {
@@ -2473,4 +2491,25 @@
             return !oldOpptCachedList.equals(mCacheOpportunisticSubInfoList);
         }
     }
+
+    private boolean shouldDisableSubGroup(String groupUuid) {
+        if (groupUuid == null) return false;
+
+        for (SubscriptionInfo activeInfo : mCacheActiveSubInfoList) {
+            if (!activeInfo.isOpportunistic() && groupUuid.equals(activeInfo.getGroupUuid())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void deactivateSubscription(SubscriptionInfo info) {
+        // TODO: b/120439488 deactivate pSIM.
+        if (info.isEmbedded()) {
+            EuiccManager euiccManager = new EuiccManager(mContext);
+            euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                    PendingIntent.getService(mContext, 0, new Intent(), 0));
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 4bf8638..e3c8cad 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -276,16 +276,8 @@
                 break;
 
             case EVENT_SIM_NOT_READY:
-                broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
-                        null);
-                broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_PRESENT);
-                broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY);
+                handleSimNotReady(msg.arg1);
                 // intentional fall through
-                // ICC_NOT_READY is a terminal state for an eSIM on the boot profile. At this
-                // phase, the subscription list is accessible.
-                // TODO(b/64216093): Clean up this special case, likely by treating NOT_READY
-                // as equivalent to ABSENT, once the rest of the system can handle it. Currently
-                // this breaks SystemUI which shows a "No SIM" icon.
 
             case EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS:
                 if (updateEmbeddedSubscriptions()) {
@@ -359,6 +351,26 @@
         }
     }
 
+    private void handleSimNotReady(int slotId) {
+        logd("handleSimNotReady: slotId: " + slotId);
+
+        IccCard iccCard = mPhone[slotId].getIccCard();
+        if (iccCard.isEmptyProfile()) {
+            // ICC_NOT_READY is a terminal state for an eSIM on the boot profile. At this
+            // phase, the subscription list is accessible. Treating NOT_READY
+            // as equivalent to ABSENT, once the rest of the system can handle it.
+            mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
+            if (isAllIccIdQueryDone()) {
+                updateSubscriptionInfoByIccId();
+            }
+        }
+
+        broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
+                null);
+        broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
+    }
+
     private void handleSimLoaded(int slotId) {
         logd("handleSimLoaded: slotId: " + slotId);
 
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 4522d8a..a8fa4b8 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -16,17 +16,21 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.XmlResourceParser;
 import android.database.Cursor;
 import android.os.Handler;
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.ServiceManager;
+import android.telephony.Rlog;
 
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -34,14 +38,149 @@
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccProfile;
 
+import dalvik.system.PathClassLoader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
 /**
  * This class has one-line methods to instantiate objects only. The purpose is to make code
  * unit-test friendly and use this class as a way to do dependency injection. Instantiating objects
  * this way makes it easier to mock them in tests.
  */
 public class TelephonyComponentFactory {
+
+    private static final String TAG = TelephonyComponentFactory.class.getSimpleName();
+
     private static TelephonyComponentFactory sInstance;
 
+    private InjectedComponents mInjectedComponents;
+
+    private static class InjectedComponents {
+        private static final String ATTRIBUTE_JAR = "jar";
+        private static final String ATTRIBUTE_PACKAGE = "package";
+        private static final String TAG_INJECTION = "injection";
+        private static final String TAG_COMPONENTS = "components";
+        private static final String TAG_COMPONENT = "component";
+
+        private final Set<String> mComponentNames = new HashSet<>();
+        private TelephonyComponentFactory mInjectedInstance;
+        private String mPackageName;
+        private String mJarPath;
+
+        private boolean isInjected() {
+            return mPackageName != null && mJarPath != null;
+        }
+
+        private void makeInjectedInstance() {
+            if (isInjected()) {
+                PathClassLoader classLoader = new PathClassLoader(mJarPath,
+                        ClassLoader.getSystemClassLoader());
+                try {
+                    Class<?> cls = classLoader.loadClass(mPackageName);
+                    mInjectedInstance = (TelephonyComponentFactory) cls.newInstance();
+                } catch (ClassNotFoundException e) {
+                    Rlog.e(TAG, "failed: " + e.getMessage());
+                } catch (IllegalAccessException | InstantiationException e) {
+                    Rlog.e(TAG, "injection failed: " + e.getMessage());
+                }
+            }
+        }
+
+        private boolean isComponentInjected(String componentName) {
+            if (mInjectedInstance == null) {
+                return false;
+            }
+            return mComponentNames.contains(componentName);
+        }
+
+        /**
+         * Find the injection tag, set attributes, and then parse the injection.
+         */
+        private void parseXml(@NonNull XmlPullParser parser) {
+            parseXmlByTag(parser, false, p -> {
+                setAttributes(p);
+                parseInjection(p);
+            }, TAG_INJECTION);
+        }
+
+        /**
+         * Only parse the first injection tag. Find the components tag, then try parse it next.
+         */
+        private void parseInjection(@NonNull XmlPullParser parser) {
+            parseXmlByTag(parser, false, p -> parseComponents(p), TAG_COMPONENTS);
+        }
+
+        /**
+         * Only parse the first components tag. Find the component tags, then try parse them next.
+         */
+        private void parseComponents(@NonNull XmlPullParser parser) {
+            parseXmlByTag(parser, true, p -> parseComponent(p), TAG_COMPONENT);
+        }
+
+        /**
+         * Extract text values from component tags.
+         */
+        private void parseComponent(@NonNull XmlPullParser parser) {
+            try {
+                int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.TEXT) {
+                        mComponentNames.add(parser.getText());
+                    }
+                }
+            } catch (XmlPullParserException | IOException e) {
+                Rlog.e(TAG, "Failed to parse the component." , e);
+            }
+        }
+
+        /**
+         * Iterates the tags, finds the corresponding tag and then applies the consumer.
+         */
+        private void parseXmlByTag(@NonNull XmlPullParser parser, boolean allowDuplicate,
+                @NonNull Consumer<XmlPullParser> consumer, @NonNull final String tag) {
+            try {
+                int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.START_TAG && tag.equals(parser.getName())) {
+                        consumer.accept(parser);
+                        if (!allowDuplicate) {
+                            return;
+                        }
+                    }
+                }
+            } catch (XmlPullParserException | IOException e) {
+                Rlog.e(TAG, "Failed to parse or find tag: " + tag, e);
+            }
+        }
+
+        /**
+         * Sets the mPackageName and mJarPath by <injection/> tag.
+         * @param parser
+         * @return
+         */
+        private void setAttributes(@NonNull XmlPullParser parser) {
+            for (int i = 0; i < parser.getAttributeCount(); i++) {
+                String name = parser.getAttributeName(i);
+                String value = parser.getAttributeValue(i);
+                if (InjectedComponents.ATTRIBUTE_PACKAGE.equals(name)) {
+                    mPackageName = value;
+                } else if (InjectedComponents.ATTRIBUTE_JAR.equals(name)) {
+                    mJarPath = value;
+                }
+            }
+        }
+    }
+
     public static TelephonyComponentFactory getInstance() {
         if (sInstance == null) {
             sInstance = new TelephonyComponentFactory();
@@ -49,6 +188,46 @@
         return sInstance;
     }
 
+    /**
+     * Inject TelephonyComponentFactory using a xml config file.
+     * @param parser a nullable {@link XmlResourceParser} created with the injection config file.
+     * The config xml should has below formats:
+     * <injection package="package.InjectedTelephonyComponentFactory" jar="path to jar file">
+     *     <components>
+     *         <component>example.package.ComponentAbc</component>
+     *         <component>example.package.ComponentXyz</component>
+     *         <!-- e.g. com.android.internal.telephony.GsmCdmaPhone -->
+     *     </components>
+     * </injection>
+     */
+    public void injectTheComponentFactory(XmlResourceParser parser) {
+        if (mInjectedComponents != null) {
+            Rlog.i(TAG, "Already injected.");
+            return;
+        }
+
+        if (parser != null) {
+            mInjectedComponents = new InjectedComponents();
+            mInjectedComponents.parseXml(parser);
+            mInjectedComponents.makeInjectedInstance();
+            Rlog.i(TAG, "Total components injected: "
+                    + mInjectedComponents.mComponentNames.size());
+        }
+    }
+
+    /**
+     * Use the injected TelephonyComponentFactory if configured. Otherwise, use the default.
+     * @param componentName Name of the component class uses the injected component factory,
+     * e.g. GsmCdmaPhone.class.getName() for {@link GsmCdmaPhone}
+     * @return injected component factory. If not configured or injected, return the default one.
+     */
+    public TelephonyComponentFactory inject(String componentName) {
+        if (mInjectedComponents != null && mInjectedComponents.isComponentInjected(componentName)) {
+            return mInjectedComponents.mInjectedInstance;
+        }
+        return sInstance;
+    }
+
     public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
         return new GsmCdmaCallTracker(phone);
     }
@@ -66,6 +245,13 @@
     }
 
     /**
+     * Create a new EmergencyNumberTracker.
+     */
+    public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci) {
+        return new EmergencyNumberTracker(phone, ci);
+    }
+
+    /**
      * Sets the NitzStateMachine implementation to use during implementation. This boolean
      * should be removed once the new implementation is stable.
      */
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index eff5c67..2e66a6d 100755
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -19,6 +19,7 @@
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
+
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -38,7 +39,6 @@
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony;
@@ -51,8 +51,6 @@
 
 import com.android.internal.telephony.uicc.IccUtils;
 
-import java.util.HashMap;
-
 import com.google.android.mms.MmsException;
 import com.google.android.mms.pdu.DeliveryInd;
 import com.google.android.mms.pdu.GenericPdu;
@@ -62,6 +60,8 @@
 import com.google.android.mms.pdu.PduPersister;
 import com.google.android.mms.pdu.ReadOrigInd;
 
+import java.util.HashMap;
+
 /**
  * WAP push handler class.
  *
@@ -133,7 +133,8 @@
 
     public WapPushOverSms(Context context) {
         mContext = context;
-        mDeviceIdleController = TelephonyComponentFactory.getInstance().getIDeviceIdleController();
+        mDeviceIdleController = TelephonyComponentFactory.getInstance()
+                .inject(IDeviceIdleController.class.getName()).getIDeviceIdleController();
 
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
 
@@ -200,9 +201,9 @@
                     return result;
                 }
             }
-
             WspTypeDecoder pduDecoder =
-                    TelephonyComponentFactory.getInstance().makeWspTypeDecoder(pdu);
+                    TelephonyComponentFactory.getInstance().inject(WspTypeDecoder.class.getName())
+                            .makeWspTypeDecoder(pdu);
 
             /**
              * Parse HeaderLen(unsigned integer).
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 46d0243..fcea2d9 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -286,8 +286,8 @@
         // pass the user data portion of the PDU to the shared handler in SMSDispatcher
         byte[] userData = new byte[pdu.length - index];
         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
-
-        InboundSmsTracker tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(
+        InboundSmsTracker tracker = TelephonyComponentFactory.getInstance()
+                .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
                 userData, timestamp, destinationPort, true, address, dispAddr, referenceNumber,
                 segment, totalSegments, true, HexDump.toHexString(userData));
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index 322b9c0..2f74ff0 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -451,7 +451,7 @@
 
             // Cleanup connections that have changed
             for (ApnContext apnContext : apnsToCleanup) {
-               mDct.sendCleanUpConnection(true, apnContext);
+                mDct.cleanUpConnection(apnContext);
             }
 
             // Retry connections that have disappeared
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index b31da9b..26284c1 100755
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -465,12 +465,6 @@
         sendMessage(msg);
     }
 
-    /**
-     * List of messages that are waiting to be posted, when data call disconnect
-     * is complete
-     */
-    private ArrayList<Message> mDisconnectAllCompleteMsgList = new ArrayList<Message>();
-
     private RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
 
     // member variables
@@ -517,10 +511,6 @@
     //        really a lower power mode")
     private boolean mIsScreenOn = true;
 
-    // Indicates if we found mvno-specific APNs in the full APN list.
-    // used to determine if we can accept mno-specific APN for tethering.
-    private boolean mMvnoMatched = false;
-
     /** Allows the generation of unique Id's for DataConnection objects */
     private AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
 
@@ -738,7 +728,7 @@
             mProvisioningSpinner = null;
         }
 
-        cleanUpAllConnections(true, null);
+        cleanUpAllConnectionsInternal(true, null);
 
         mIsDisposed = true;
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
@@ -809,7 +799,7 @@
                 reevaluateDataConnections();
                 onTrySetupData(Phone.REASON_DATA_ENABLED);
             } else {
-                onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+                cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
             }
         }
     }
@@ -840,7 +830,7 @@
             reevaluateDataConnections();
             onTrySetupData(Phone.REASON_DATA_ENABLED);
         } else {
-            onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
         }
     }
 
@@ -852,7 +842,7 @@
             reevaluateDataConnections();
             onTrySetupData(Phone.REASON_DATA_ENABLED);
         } else {
-            onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
         }
     }
 
@@ -1516,17 +1506,33 @@
     }
 
     /**
-     * If tearDown is true, this only tears down a CONNECTED session. Presently,
-     * there is no mechanism for abandoning an CONNECTING session,
-     * but would likely involve cancelling pending async requests or
-     * setting a flag or new state to ignore them when they came in
-     * @param tearDown true if the underlying DataConnection should be
-     * disconnected.
+     * Clean up all data connections. Note this is just detach the APN context from the data
+     * connection. After all APN contexts are detached from the data connection, the data
+     * connection will be torn down.
+     *
+     * @param reason Reason for the clean up.
+     */
+    public void cleanUpAllConnections(String reason) {
+        log("cleanUpAllConnections");
+        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
+        msg.obj = reason;
+        sendMessage(msg);
+    }
+
+    /**
+     * Clean up all data connections by detaching the APN contexts from the data connections, which
+     * eventually tearing down all data connections after all APN contexts are detached from the
+     * data connections.
+     *
+     * @param tearDown True if the underlying data connection should be disconnected when no
+     * APN context attached to the data connection. False if we only want to reset the data
+     * connection's state machine without requesting modem to tearing down the data connections.
+     *
      * @param reason reason for the clean up.
      * @return boolean - true if we did cleanup any connections, false if they
      *                   were already all disconnected.
      */
-    private boolean cleanUpAllConnections(boolean tearDown, String reason) {
+    private boolean cleanUpAllConnectionsInternal(boolean tearDown, String reason) {
         if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
         boolean didDisconnect = false;
         boolean disableMeteredOnly = false;
@@ -1548,7 +1554,7 @@
                     if (apnContext.isDisconnected() == false) didDisconnect = true;
                     if (DBG) log("clean up metered ApnContext Type: " + apnContext.getApnType());
                     apnContext.setReason(reason);
-                    cleanUpConnection(tearDown, apnContext);
+                    cleanUpConnectionInternal(tearDown, apnContext);
                 }
             } else {
                 // Exclude the IMS APN from single DataConenction case.
@@ -1559,7 +1565,7 @@
                 // TODO - only do cleanup if not disconnected
                 if (apnContext.isDisconnected() == false) didDisconnect = true;
                 apnContext.setReason(reason);
-                cleanUpConnection(tearDown, apnContext);
+                cleanUpConnectionInternal(tearDown, apnContext);
             }
         }
 
@@ -1569,9 +1575,8 @@
         // TODO: Do we need mRequestedApnType?
         mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
-        log("cleanUpConnection: mDisconnectPendingCount = " + mDisconnectPendingCount);
+        log("cleanUpConnectionInternal: mDisconnectPendingCount = " + mDisconnectPendingCount);
         if (tearDown && mDisconnectPendingCount == 0) {
-            notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
         }
 
@@ -1579,36 +1584,36 @@
     }
 
     /**
-     * Cleanup all connections.
+     * Detach the APN context from the associated data connection. This data connection might be
+     * torn down if no other APN context is attached to it.
      *
-     * TODO: Cleanup only a specified connection passed as a parameter.
-     *       Also, make sure when you clean up a conn, if it is last apply
-     *       logic as though it is cleanupAllConnections
-     *
-     * @param cause for the clean up.
+     * @param apnContext The APN context to be detached
      */
-    private void onCleanUpAllConnections(String cause) {
-        cleanUpAllConnections(true, cause);
-    }
-
-    void sendCleanUpConnection(boolean tearDown, ApnContext apnContext) {
-        if (DBG) log("sendCleanUpConnection: tearDown=" + tearDown + " apnContext=" + apnContext);
+    void cleanUpConnection(ApnContext apnContext) {
+        if (DBG) log("cleanUpConnection: apnContext=" + apnContext);
         Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_CONNECTION);
-        msg.arg1 = tearDown ? 1 : 0;
         msg.arg2 = 0;
         msg.obj = apnContext;
         sendMessage(msg);
     }
 
-    private void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
+    /**
+     * Detach the APN context from the associated data connection. This data connection might be
+     * torn down if no other APN context is attached to it.
+     *
+     * @param tearDown True if tearing down data connection when no other APN context attached.
+     * False to only reset the data connection's state machine.
+     * @param apnContext The APN context to be detached
+     */
+    private void cleanUpConnectionInternal(boolean tearDown, ApnContext apnContext) {
         if (apnContext == null) {
-            if (DBG) log("cleanUpConnection: apn context is null");
+            if (DBG) log("cleanUpConnectionInternal: apn context is null");
             return;
         }
 
         DataConnection dataConnection = apnContext.getDataConnection();
-        String str = "cleanUpConnection: tearDown=" + tearDown + " reason=" +
-                apnContext.getReason();
+        String str = "cleanUpConnectionInternal: tearDown=" + tearDown + " reason="
+                + apnContext.getReason();
         if (VDBG) log(str + " apnContext=" + apnContext);
         apnContext.requestLog(str);
         if (tearDown) {
@@ -1618,7 +1623,7 @@
                 apnContext.setState(DctConstants.State.IDLE);
                 if (!apnContext.isReady()) {
                     if (dataConnection != null) {
-                        str = "cleanUpConnection: teardown, disconnected, !ready";
+                        str = "cleanUpConnectionInternal: teardown, disconnected, !ready";
                         if (DBG) log(str + " apnContext=" + apnContext);
                         apnContext.requestLog(str);
                         dataConnection.tearDown(apnContext, "", null);
@@ -1635,7 +1640,7 @@
                             // if (PhoneConstants.APN_TYPE_DUN.equals(PhoneConstants.APN_TYPE_DEFAULT)) {
                             if (teardownForDun()) {
                                 if (DBG) {
-                                    log("cleanUpConnection: disconnectAll DUN connection");
+                                    log("cleanUpConnectionInternal: disconnectAll DUN connection");
                                 }
                                 // we need to tear it down - we brought it up just for dun and
                                 // other people are camped on it and now dun is done.  We need
@@ -1645,12 +1650,11 @@
                             }
                         }
                         final int generation = apnContext.getConnectionGeneration();
-                        str = "cleanUpConnection: tearing down" + (disconnectAll ? " all" : "") +
-                                " using gen#" + generation;
+                        str = "cleanUpConnectionInternal: tearing down"
+                                + (disconnectAll ? " all" : "") + " using gen#" + generation;
                         if (DBG) log(str + "apnContext=" + apnContext);
                         apnContext.requestLog(str);
-                        Pair<ApnContext, Integer> pair =
-                                new Pair<ApnContext, Integer>(apnContext, generation);
+                        Pair<ApnContext, Integer> pair = new Pair<>(apnContext, generation);
                         Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
 
                         if (disconnectAll) {
@@ -1666,7 +1670,7 @@
                     // apn is connected but no reference to the data connection.
                     // Should not be happen, but reset the state in case.
                     apnContext.setState(DctConstants.State.IDLE);
-                    apnContext.requestLog("cleanUpConnection: connected, bug no dc");
+                    apnContext.requestLog("cleanUpConnectionInternal: connected, bug no dc");
                     mPhone.notifyDataConnection(apnContext.getReason(),
                                                 apnContext.getApnType());
                 }
@@ -1684,7 +1688,8 @@
         if (dataConnection != null) {
             cancelReconnectAlarm(apnContext);
         }
-        str = "cleanUpConnection: X tearDown=" + tearDown + " reason=" + apnContext.getReason();
+        str = "cleanUpConnectionInternal: X tearDown=" + tearDown + " reason="
+                + apnContext.getReason();
         if (DBG) log(str + " apnContext=" + apnContext + " dc=" + apnContext.getDataConnection());
         apnContext.requestLog(str);
     }
@@ -1700,8 +1705,6 @@
             return new ArrayList<ApnSetting>(0);
         }
         int bearer = mPhone.getServiceState().getRilDataRadioTechnology();
-        IccRecords r = mIccRecords.get();
-        String operator = (r != null) ? r.getOperatorNumeric() : "";
         ArrayList<ApnSetting> dunCandidates = new ArrayList<ApnSetting>();
         ArrayList<ApnSetting> retDunSettings = new ArrayList<ApnSetting>();
 
@@ -1729,16 +1732,7 @@
                     ServiceState.rilRadioTechnologyToNetworkType(bearer))) {
                 continue;
             }
-            if (dunSetting.getOperatorNumeric().equals(operator)) {
-                if (dunSetting.hasMvnoParams()) {
-                    if (r != null && ApnSettingUtils.mvnoMatches(r, dunSetting.getMvnoType(),
-                            dunSetting.getMvnoMatchData())) {
-                        retDunSettings.add(dunSetting);
-                    }
-                } else if (mMvnoMatched == false) {
-                    retDunSettings.add(dunSetting);
-                }
-            }
+            retDunSettings.add(dunSetting);
         }
 
         if (VDBG) log("fetchDunApns: dunSettings=" + retDunSettings);
@@ -1811,41 +1805,6 @@
                 (mAttached.get() == false || dcFailCause != DcFailCause.SIGNAL_LOST));
     }
 
-    private ArrayList<ApnSetting> createApnList(Cursor cursor) {
-        ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>();
-        ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>();
-        IccRecords r = mIccRecords.get();
-
-        if (cursor.moveToFirst()) {
-            do {
-                ApnSetting apn = ApnSetting.makeApnSetting(cursor);
-                if (apn == null) {
-                    continue;
-                }
-
-                if (apn.hasMvnoParams()) {
-                    if (r != null && ApnSettingUtils.mvnoMatches(r, apn.getMvnoType(),
-                            apn.getMvnoMatchData())) {
-                        mvnoApns.add(apn);
-                    }
-                } else {
-                    mnoApns.add(apn);
-                }
-            } while (cursor.moveToNext());
-        }
-
-        ArrayList<ApnSetting> result;
-        if (mvnoApns.isEmpty()) {
-            result = mnoApns;
-            mMvnoMatched = false;
-        } else {
-            result = mvnoApns;
-            mMvnoMatched = true;
-        }
-        if (DBG) log("createApnList: X result=" + result);
-        return result;
-    }
-
     private DataConnection findFreeDataConnection() {
         for (DataConnection dataConnection : mDataConnections.values()) {
             boolean inUse = false;
@@ -1924,7 +1883,7 @@
                     // Only lower priority calls left.  Disconnect them all in this single PDP case
                     // so that we can bring up the requested higher priority call (once we receive
                     // response for deactivate request for the calls we are about to disconnect
-                    if (cleanUpAllConnections(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
+                    if (cleanUpAllConnectionsInternal(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
                         // If any call actually requested to be disconnected, means we can't
                         // bring up this connection yet as we need to wait for those data calls
                         // to be disconnected.
@@ -2123,7 +2082,7 @@
 
     private void restartRadio() {
         if (DBG) log("restartRadio: ************TURN OFF RADIO**************");
-        cleanUpAllConnections(true, Phone.REASON_RADIO_TURNED_OFF);
+        cleanUpAllConnectionsInternal(true, Phone.REASON_RADIO_TURNED_OFF);
         mPhone.getServiceStateTracker().powerOffRadioSafely();
         /* Note: no need to call setRadioPower(true).  Assuming the desired
          * radio power state is still ON (as tracked by ServiceStateTracker),
@@ -2232,7 +2191,8 @@
                 // Send otasp_sim_unprovisioned so that SuW is able to proceed and notify users
                 mPhone.notifyOtaspChanged(TelephonyManager.OTASP_SIM_UNPROVISIONED);
                 // Tear down all metered apns
-                cleanUpAllConnections(true, Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
+                cleanUpAllConnectionsInternal(true,
+                        Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
             } else {
                 // Re-evaluate Otasp state
                 int otaspState = mPhone.getServiceStateTracker().getOtasp();
@@ -2247,7 +2207,7 @@
     private void onSimNotReady() {
         if (DBG) log("onSimNotReady");
 
-        cleanUpAllConnections(true, Phone.REASON_SIM_NOT_READY);
+        cleanUpAllConnectionsInternal(true, Phone.REASON_SIM_NOT_READY);
         mAllApnSettings.clear();
         mAutoAttachOnCreationConfig = false;
         // Clear auto attach as modem is expected to do a new attach once SIM is ready
@@ -2277,7 +2237,7 @@
                     reevaluateDataConnections();
                     onTrySetupData(Phone.REASON_DATA_ENABLED);
                 } else {
-                    onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+                    cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
                 }
             }
         }
@@ -2355,7 +2315,7 @@
         }
         apnContext.setEnabled(enabled);
         apnContext.setDependencyMet(met);
-        if (cleanup) cleanUpConnection(true, apnContext);
+        if (cleanup) cleanUpConnectionInternal(true, apnContext);
         if (trySetup) {
             apnContext.resetErrorCodeRetries();
             trySetupData(apnContext);
@@ -2669,7 +2629,7 @@
             // roaming, we need to tear down the data connection otherwise the user might be
             // charged for data roaming usage.
             if (DBG) log("onDataRoamingOnOrSettingsChanged: Tear down data connection on roaming.");
-            cleanUpAllConnections(true, Phone.REASON_ROAMING_ON);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_ROAMING_ON);
             notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
         }
     }
@@ -2707,7 +2667,7 @@
         }
 
         if (getOverallState() != DctConstants.State.IDLE) {
-            cleanUpConnection(true, null);
+            cleanUpConnectionInternal(true, null);
         }
     }
 
@@ -2726,7 +2686,7 @@
             log("We're on the simulator; assuming radio off is meaningless");
         } else {
             if (DBG) log("onRadioOffOrNotAvailable: is off and clean up all connections");
-            cleanUpAllConnections(false, Phone.REASON_RADIO_TURNED_OFF);
+            cleanUpAllConnectionsInternal(false, Phone.REASON_RADIO_TURNED_OFF);
         }
         notifyOffApnsOfAvailability(null);
     }
@@ -2958,7 +2918,7 @@
          * to clean data connections.
          */
         if (!mDataEnabledSettings.isInternalDataEnabled()) {
-            cleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_DISABLED);
         }
 
     }
@@ -3058,7 +3018,6 @@
                 }
 
                 if (mDisconnectPendingCount == 0) {
-                    notifyDataDisconnectComplete();
                     notifyAllDataDisconnected();
                 }
                 return;
@@ -3105,7 +3064,6 @@
         if (mDisconnectPendingCount == 0) {
             apnContext.setConcurrentVoiceAndDataAllowed(
                     mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed());
-            notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
         }
 
@@ -3158,7 +3116,7 @@
         ApnContext apnContext = mApnContextsByType.get(apnType);
         if (apnContext != null) {
             apnContext.setReason(reason);
-            cleanUpConnection(tearDown, apnContext);
+            cleanUpConnectionInternal(tearDown, apnContext);
         }
     }
 
@@ -3224,34 +3182,28 @@
      * Data Connections and setup the preferredApn.
      */
     private void createAllApnList() {
-        mMvnoMatched = false;
         mAllApnSettings.clear();
         IccRecords r = mIccRecords.get();
         String operator = (r != null) ? r.getOperatorNumeric() : "";
-        if (operator != null) {
-            String selection = Telephony.Carriers.NUMERIC + " = '" + operator + "'";
-            // query only enabled apn.
-            // carrier_enabled : 1 means enabled apn, 0 disabled apn.
-            // selection += " and carrier_enabled = 1";
-            if (DBG) log("createAllApnList: selection=" + selection);
 
-            // ORDER BY Telephony.Carriers._ID ("_id")
-            Cursor cursor = mPhone.getContext().getContentResolver().query(
-                    Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "filtered"),
-                    null, selection, null, Telephony.Carriers._ID);
+        // ORDER BY Telephony.Carriers._ID ("_id")
+        Cursor cursor = mPhone.getContext().getContentResolver().query(
+                Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "filtered/subId/"
+                        + mPhone.getSubId()), null, null, null, Telephony.Carriers._ID);
 
-            if (cursor != null) {
-                if (cursor.getCount() > 0) {
-                    mAllApnSettings = createApnList(cursor);
-                } else {
-                    if (DBG) log("createAllApnList: cursor count is 0");
-                    mApnSettingsInitializationLog.log("no APN in db for carrier: " + operator);
+        if (cursor != null) {
+            while (cursor.moveToNext()) {
+                ApnSetting apn = ApnSetting.makeApnSetting(cursor);
+                if (apn == null) {
+                    continue;
                 }
-                cursor.close();
-            } else {
-                if (DBG) log("createAllApnList: cursor is null");
-                mApnSettingsInitializationLog.log("cursor is null for carrier: " + operator);
+                mAllApnSettings.add(apn);
             }
+            cursor.close();
+        } else {
+            if (DBG) log("createAllApnList: cursor is null");
+            mApnSettingsInitializationLog.log("cursor is null for carrier, operator: "
+                    + operator);
         }
 
         addEmergencyApnSetting();
@@ -3259,8 +3211,9 @@
         dedupeApnSettings();
 
         if (mAllApnSettings.isEmpty()) {
-            if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
-            mApnSettingsInitializationLog.log("no APN found for carrier: " + operator);
+            log("createAllApnList: No APN found for carrier, operator: " + operator);
+            mApnSettingsInitializationLog.log("no APN found for carrier, operator: "
+                    + operator);
             mPreferredApn = null;
             // TODO: What is the right behavior?
             //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
@@ -3613,7 +3566,7 @@
                 } else {
                     // TODO: Should all PDN states be checked to fail?
                     if (mState == DctConstants.State.FAILED) {
-                        cleanUpAllConnections(false, Phone.REASON_PS_RESTRICT_ENABLED);
+                        cleanUpAllConnectionsInternal(false, Phone.REASON_PS_RESTRICT_ENABLED);
                         mReregisterOnReconnectFailure = false;
                     }
                     ApnContext apnContext = mApnContextsByType.get(ApnSetting.TYPE_DEFAULT);
@@ -3640,24 +3593,19 @@
                 break;
 
             case DctConstants.EVENT_CLEAN_UP_CONNECTION:
-                boolean tearDown = (msg.arg1 == 0) ? false : true;
-                if (DBG) log("EVENT_CLEAN_UP_CONNECTION tearDown=" + tearDown);
-                if (msg.obj instanceof ApnContext) {
-                    cleanUpConnection(tearDown, (ApnContext)msg.obj);
-                } else {
-                    onCleanUpConnection(tearDown, msg.arg2, (String) msg.obj);
-                }
+                if (DBG) log("EVENT_CLEAN_UP_CONNECTION");
+                cleanUpConnectionInternal(true, (ApnContext) msg.obj);
                 break;
             case DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE: {
                 final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
-                onSetInternalDataEnabled(enabled, (Message) msg.obj);
+                onSetInternalDataEnabled(enabled);
                 break;
             }
             case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS:
                 if ((msg.obj != null) && (msg.obj instanceof String == false)) {
                     msg.obj = null;
                 }
-                onCleanUpAllConnections((String) msg.obj);
+                cleanUpAllConnectionsInternal(true, (String) msg.obj);
                 break;
 
             case DctConstants.EVENT_DATA_RAT_CHANGED:
@@ -3824,7 +3772,7 @@
                         mIsProvisioning = false;
                         mProvisioningUrl = null;
                         stopProvisioningApnAlarm();
-                        sendCleanUpConnection(true, apnCtx);
+                        cleanUpConnectionInternal(true, apnCtx);
                     } else {
                         if (DBG) {
                             log("EVENT_PROVISIONING_APN_ALARM: ignore stale tag,"
@@ -3959,6 +3907,11 @@
         }
     }
 
+    /**
+     * Update DcTracker.
+     *
+     * TODO: This should be cleaned up. DcTracker should listen to those events.
+     */
     public void update() {
         log("update sub = " + mPhone.getSubId());
         log("update(): Active DDS, register for all events now!");
@@ -3966,33 +3919,9 @@
 
         mAutoAttachOnCreation.set(false);
 
-        ((GsmCdmaPhone)mPhone).updateCurrentCarrierInProvider();
+        mPhone.updateCurrentCarrierInProvider();
     }
 
-    public void cleanUpAllConnections(String cause) {
-        cleanUpAllConnections(cause, null);
-    }
-
-    public void cleanUpAllConnections(String cause, Message disconnectAllCompleteMsg) {
-        log("cleanUpAllConnections");
-        if (disconnectAllCompleteMsg != null) {
-            mDisconnectAllCompleteMsgList.add(disconnectAllCompleteMsg);
-        }
-
-        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
-        msg.obj = cause;
-        sendMessage(msg);
-    }
-
-    private void notifyDataDisconnectComplete() {
-        log("notifyDataDisconnectComplete");
-        for (Message m: mDisconnectAllCompleteMsgList) {
-            m.sendToTarget();
-        }
-        mDisconnectAllCompleteMsgList.clear();
-    }
-
-
     private void notifyAllDataDisconnected() {
         sEnableFailFastRefCounter = 0;
         mFailFast = false;
@@ -4020,35 +3949,22 @@
         mDataEnabledSettings.unregisterForDataEnabledChanged(h);
     }
 
-    private void onSetInternalDataEnabled(boolean enabled, Message onCompleteMsg) {
+    private void onSetInternalDataEnabled(boolean enabled) {
         if (DBG) log("onSetInternalDataEnabled: enabled=" + enabled);
-        boolean sendOnComplete = true;
-
         mDataEnabledSettings.setInternalDataEnabled(enabled);
         if (enabled) {
             log("onSetInternalDataEnabled: changed to enabled, try to setup data call");
             onTrySetupData(Phone.REASON_DATA_ENABLED);
         } else {
-            sendOnComplete = false;
             log("onSetInternalDataEnabled: changed to disabled, cleanUpAllConnections");
-            cleanUpAllConnections(Phone.REASON_DATA_DISABLED, onCompleteMsg);
-        }
-
-        if (sendOnComplete) {
-            if (onCompleteMsg != null) {
-                onCompleteMsg.sendToTarget();
-            }
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_DISABLED);
         }
     }
 
     public boolean setInternalDataEnabled(boolean enable) {
-        return setInternalDataEnabled(enable, null);
-    }
-
-    public boolean setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
         if (DBG) log("setInternalDataEnabled(" + enable + ")");
 
-        Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE, onCompleteMsg);
+        Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE);
         msg.arg1 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
         sendMessage(msg);
         return true;
@@ -4258,7 +4174,7 @@
     private void cleanUpConnectionsOnUpdatedApns(boolean tearDown, String reason) {
         if (DBG) log("cleanUpConnectionsOnUpdatedApns: tearDown=" + tearDown);
         if (mAllApnSettings.isEmpty()) {
-            cleanUpAllConnections(tearDown, Phone.REASON_APN_CHANGED);
+            cleanUpAllConnectionsInternal(tearDown, Phone.REASON_APN_CHANGED);
         } else {
             int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
             if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
@@ -4284,7 +4200,7 @@
                     if (!apnContext.isDisconnected()) {
                         if (VDBG) log("cleanUpConnectionsOnUpdatedApns for " + apnContext);
                         apnContext.setReason(reason);
-                        cleanUpConnection(true, apnContext);
+                        cleanUpConnectionInternal(true, apnContext);
                     }
                 }
             }
@@ -4299,7 +4215,6 @@
 
         if (DBG) log("mDisconnectPendingCount = " + mDisconnectPendingCount);
         if (tearDown && mDisconnectPendingCount == 0) {
-            notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
         }
     }
@@ -4519,7 +4434,7 @@
                     EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
                             mSentSinceLastRecv);
                     if (DBG) log("doRecovery() cleanup all connections");
-                    cleanUpAllConnections(Phone.REASON_PDP_RESET);
+                    cleanUpAllConnectionsInternal(true, Phone.REASON_PDP_RESET);
                     putRecoveryAction(RecoveryAction.REREGISTER);
                     break;
                 case RecoveryAction.REREGISTER:
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
new file mode 100644
index 0000000..5425f2b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.emergency;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.emergency.EmergencyNumber;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.ecc.nano.ProtobufEccData;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Emergency Number Tracker that handles update of emergency number list from RIL and emergency
+ * number database. This is multi-sim based and each Phone has a EmergencyNumberTracker.
+ */
+public class EmergencyNumberTracker extends Handler {
+    private static final String TAG = EmergencyNumberTracker.class.getSimpleName();
+
+    /** @hide */
+    public static boolean DBG = false;
+
+    private final CommandsInterface mCi;
+    private final Phone mPhone;
+    private List<EmergencyNumber> mEmergencyNumberListFromDatabase = new ArrayList<>();
+    private List<EmergencyNumber> mEmergencyNumberListFromRadio = new ArrayList<>();
+    private List<EmergencyNumber> mEmergencyNumberList = new ArrayList<>();
+
+    private final LocalLog mEmergencyNumberListDatabaseLocalLog = new LocalLog(20);
+    private final LocalLog mEmergencyNumberListRadioLocalLog = new LocalLog(20);
+    private final LocalLog mEmergencyNumberListLocalLog = new LocalLog(20);
+
+    /** Event indicating the update for the emergency number list from the radio. */
+    private static final int EVENT_UNSOL_EMERGENCY_NUMBER_LIST = 1;
+
+    // TODO EVENT_UPDATE_NETWORK_COUNTRY_ISO
+
+    public EmergencyNumberTracker(Phone phone, CommandsInterface ci) {
+        mPhone = phone;
+        mCi = ci;
+        // TODO cache Emergency Number List Database per country ISO;
+        // TODO register for Locale Tracker Country ISO Change
+        mCi.registerForEmergencyNumberList(this, EVENT_UNSOL_EMERGENCY_NUMBER_LIST, null);
+    }
+
+    /**
+     * Message handler for updating emergency number list from RIL, updating emergency number list
+     * from database if the country ISO is changed, and notifying the change of emergency number
+     * list.
+     *
+     * @param msg The message
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case EVENT_UNSOL_EMERGENCY_NUMBER_LIST:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Result from RIL is null.");
+                } else if ((ar.result != null) && (ar.exception == null)) {
+                    updateAndNotifyEmergencyNumberList((List<EmergencyNumber>) ar.result);
+                } else {
+                    loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Exception from RIL : "
+                            + ar.exception);
+                }
+                break;
+        }
+    }
+
+    private void updateAndNotifyEmergencyNumberList(
+            List<EmergencyNumber> emergencyNumberListRadio) {
+        Collections.sort(emergencyNumberListRadio);
+        logd("updateAndNotifyEmergencyNumberList(): receiving " + emergencyNumberListRadio);
+
+        if (!emergencyNumberListRadio.equals(mEmergencyNumberListFromRadio)) {
+            try {
+                mEmergencyNumberListFromRadio = emergencyNumberListRadio;
+                if (!DBG) {
+                    mEmergencyNumberListRadioLocalLog.log("updateRadioEmergencyNumberList:"
+                            + emergencyNumberListRadio);
+                }
+                List<EmergencyNumber> emergencyNumberListMergedWithDatabase =
+                        constructEmergencyNumberListWithDatabase();
+                mEmergencyNumberList = emergencyNumberListMergedWithDatabase;
+                if (!DBG) {
+                    mEmergencyNumberListLocalLog.log("updateEmergencyNumberList:"
+                            + emergencyNumberListMergedWithDatabase);
+                }
+                notifyEmergencyNumberList();
+            } catch (NullPointerException ex) {
+                loge("updateAndNotifyEmergencyNumberList() Phone already destroyed: " + ex
+                        + "EmergencyNumberList not notified");
+            }
+        }
+    }
+
+    private void notifyEmergencyNumberList() {
+        List<EmergencyNumber> emergencyNumberListToNotify = getEmergencyNumberList();
+        mPhone.notifyEmergencyNumberList(emergencyNumberListToNotify);
+        logd("notifyEmergencyNumberList():" + emergencyNumberListToNotify);
+    }
+
+    private List<EmergencyNumber> constructEmergencyNumberListWithDatabase() {
+        List<EmergencyNumber> emergencyNumberListRadioAndDatabase = mEmergencyNumberListFromRadio;
+        // TODO integrate with emergency number database
+        // TODO sorting
+        return emergencyNumberListRadioAndDatabase;
+    }
+
+    public List<EmergencyNumber> getEmergencyNumberList() {
+        return new ArrayList<>(mEmergencyNumberList);
+    }
+
+    @VisibleForTesting
+    public List<EmergencyNumber> getRadioEmergencyNumberList() {
+        return new ArrayList<>(mEmergencyNumberListFromRadio);
+    }
+
+    private static void logd(String str) {
+        Rlog.d(TAG, str);
+    }
+
+    private static void loge(String str) {
+        Rlog.e(TAG, str);
+    }
+
+    /**
+     * Dump Emergency Number List info in the tracking
+     *
+     * @param fd FileDescriptor
+     * @param pw PrintWriter
+     * @param args args
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.println("mEmergencyNumberListDatabaseLocalLog:");
+        ipw.increaseIndent();
+        mEmergencyNumberListDatabaseLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+        ipw.println("   -   -   -   -   -   -   -   -");
+
+        ipw.println("mEmergencyNumberListRadioLocalLog:");
+        ipw.increaseIndent();
+        mEmergencyNumberListRadioLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+        ipw.println("   -   -   -   -   -   -   -   -");
+
+        ipw.println("mEmergencyNumberListLocalLog:");
+        ipw.increaseIndent();
+        mEmergencyNumberListLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+
+        ipw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index ea5f671..ab39212 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -256,8 +256,11 @@
         // setting the multiendpoint listener on the external call tracker.  So we need to ensure
         // the external call tracker is available first to avoid potential timing issues.
         mExternalCallTracker =
-                TelephonyComponentFactory.getInstance().makeImsExternalCallTracker(this);
-        mCT = TelephonyComponentFactory.getInstance().makeImsPhoneCallTracker(this);
+                TelephonyComponentFactory.getInstance()
+                        .inject(ImsExternalCallTracker.class.getName())
+                        .makeImsExternalCallTracker(this);
+        mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName())
+                .makeImsPhoneCallTracker(this);
         mCT.registerPhoneStateListener(mExternalCallTracker);
         mExternalCallTracker.setCallPuller(mCT);
 
diff --git a/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java b/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
index 8001eb6..f8d519c 100644
--- a/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
@@ -60,9 +60,16 @@
             m.rxTimeMs = stats.getRxTimeMs();
             long[] t = stats.getTxTimeMs();
             m.txTimeMs = new long[t.length];
-            for (int i = 0; i < t.length; i++) {
-                m.txTimeMs[i] = t[i];
-            }
+            System.arraycopy(t, 0, m.txTimeMs, 0, t.length);
+            m.numBytesTx = stats.getNumBytesTx();
+            m.numPacketsRx = stats.getNumPacketsRx();
+            m.numBytesRx = stats.getNumBytesRx();
+            long[] tr = stats.getTimeInRatMs();
+            m.timeInRatMs = new long[tr.length];
+            System.arraycopy(tr, 0, m.timeInRatMs, 0, tr.length);
+            long[] trx = stats.getTimeInRxSignalStrengthLevelMs();
+            m.timeInRxSignalStrengthLevelMs = new long[trx.length];
+            System.arraycopy(trx, 0, m.timeInRxSignalStrengthLevelMs, 0, trx.length);
         }
         return m;
     }
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index c01ea78..97a6817 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -433,14 +433,22 @@
         pw.println("Power log duration (battery time) (ms): " + s.loggingDurationMs);
         pw.println("Energy consumed by modem (mAh): " + s.energyConsumedMah);
         pw.println("Number of packets sent (tx): " + s.numPacketsTx);
-        pw.println("Amount of time kernel is active because of cellular data (ms): " +
-            s.cellularKernelActiveTimeMs);
-        pw.println("Amount of time spent in very poor rx signal level (ms): " +
-            s.timeInVeryPoorRxSignalLevelMs);
+        pw.println("Number of bytes sent (tx): " + s.numBytesTx);
+        pw.println("Number of packets received (rx): " + s.numPacketsRx);
+        pw.println("Number of bytes received (rx): " + s.numBytesRx);
+        pw.println("Amount of time kernel is active because of cellular data (ms): "
+                + s.cellularKernelActiveTimeMs);
+        pw.println("Amount of time spent in very poor rx signal level (ms): "
+                + s.timeInVeryPoorRxSignalLevelMs);
         pw.println("Amount of time modem is in sleep (ms): " + s.sleepTimeMs);
         pw.println("Amount of time modem is in idle (ms): " + s.idleTimeMs);
         pw.println("Amount of time modem is in rx (ms): " + s.rxTimeMs);
         pw.println("Amount of time modem is in tx (ms): " + Arrays.toString(s.txTimeMs));
+        pw.println("Amount of time phone spent in various Radio Access Technologies (ms): "
+                + Arrays.toString(s.timeInRatMs));
+        pw.println("Amount of time phone spent in various cellular "
+                + "rx signal strength levels (ms): "
+                + Arrays.toString(s.timeInRxSignalStrengthLevelMs));
         pw.decreaseIndent();
     }
 
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index 4460489..5cb130b 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -1422,6 +1422,14 @@
     }
 
     @Override
+    public void registerForEmergencyNumberList(Handler h, int what, Object obj) {
+    }
+
+    @Override
+    public void unregisterForEmergencyNumberList(Handler h) {
+    }
+
+    @Override
     public void startNattKeepalive(
             int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index aae7874..2cf19f9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -85,7 +85,8 @@
 
             if (mCardState != CardState.CARDSTATE_ABSENT) {
                 if (mUiccProfile == null) {
-                    mUiccProfile = TelephonyComponentFactory.getInstance().makeUiccProfile(
+                    mUiccProfile = TelephonyComponentFactory.getInstance()
+                            .inject(UiccProfile.class.getName()).makeUiccProfile(
                             mContext, mCi, ics, mPhoneId, this, mLock);
                 } else {
                     mUiccProfile.update(mContext, mCi, ics);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index ce4a5ef..7215f51 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -824,6 +824,16 @@
     }
 
     @Override
+    public boolean isEmptyProfile() {
+        // If there's no UiccCardApplication, it's an empty profile.
+        // Empty profile is a valid case of eSIM (default boot profile).
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null) return false;
+        }
+        return true;
+    }
+
+    @Override
     public void setIccLockEnabled(boolean enabled, String password, Message onComplete) {
         synchronized (mLock) {
             if (mUiccApplication != null) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
index b2ee494..c604cf0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
@@ -157,7 +157,7 @@
         expectedState = new NetworkRegistrationState(
                 domain, AccessNetworkConstants.TransportType.WWAN, voiceRegState,
                 ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
-                false, availableServices, null, maxDataCalls, false, false);
+                false, availableServices, null, maxDataCalls, false, false, false);
 
         try {
             verify(mCallback, times(1)).onGetNetworkRegistrationStateComplete(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
index 1f883b8..1f2124b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.times;
@@ -33,14 +34,14 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.support.test.filters.FlakyTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Test;
 
 import java.util.ArrayList;
 
-@Ignore
 public class DeviceStateMonitorTest extends TelephonyTest {
 
     private DeviceStateMonitor mDSM;
@@ -132,4 +133,41 @@
         verify(mSimulatedCommandsVerifier, times(1)).setUnsolResponseFilter(eq(-1),
                 nullable(Message.class));
     }
+
+    private void sendStates(int screenState, int chargingState, int wifiState) {
+        setReady(false);
+        mDSM.obtainMessage(
+                DeviceStateMonitor.EVENT_SCREEN_STATE_CHANGED, screenState, 0).sendToTarget();
+        mDSM.obtainMessage(
+                DeviceStateMonitor.EVENT_CHARGING_STATE_CHANGED, chargingState, 0).sendToTarget();
+        mDSM.obtainMessage(
+                DeviceStateMonitor.EVENT_WIFI_CONNECTION_CHANGED, wifiState, 0).sendToTarget();
+        mDSM.post(() -> setReady(true));
+        waitUntilReady();
+    }
+
+    @Test
+    @MediumTest
+    public void testWifi() throws Exception  {
+        // screen off
+        sendStates(0, 0, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+        // screen off, but charging
+        sendStates(0, 1, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+        // screen on, no wifi
+        sendStates(1, 0, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_SHORT_MS, mDSM.computeCellInfoMinInterval());
+        // screen on, but on wifi
+        sendStates(1, 0, 1);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+        // screen on, charging
+        sendStates(1, 1, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_SHORT_MS, mDSM.computeCellInfoMinInterval());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index 16e0656..c8524a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -291,7 +291,7 @@
 
         NetworkRegistrationState wwanDataRegState = new NetworkRegistrationState(
                 NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
-                0, 0, 0, false, null, null, 0, false, false);
+                0, 0, 0, false, null, null, 0, false, false, false);
 
         NetworkRegistrationState wlanRegState = new NetworkRegistrationState(
                 NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WLAN,
@@ -313,7 +313,7 @@
 
         wwanDataRegState = new NetworkRegistrationState(
                 NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
-                0, 0, 0, true, null, null, 0, false, false);
+                0, 0, 0, true, null, null, 0, false, false, false);
         ss.addNetworkRegistrationState(wwanDataRegState);
         assertEquals(ss.getNetworkRegistrationStates(NetworkRegistrationState.DOMAIN_PS,
                 AccessNetworkConstants.TransportType.WWAN), wwanDataRegState);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 091d6dd..2fa4d3f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -1673,7 +1673,7 @@
 
     private void changeRegState(int state, CellIdentity cid, int voiceRat, int dataRat) {
         NetworkRegistrationState dataResult = new NetworkRegistrationState(
-                0, 0, state, dataRat, 0, false, null, cid, 1, false, false);
+                0, 0, state, dataRat, 0, false, null, cid, 1, false, false, false);
         sst.mPollingContext[0] = 2;
         // update data reg state to be in service
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
@@ -1762,7 +1762,7 @@
     private void sendRegStateUpdateForLteCellId(CellIdentityLte cellId) {
         NetworkRegistrationState dataResult = new NetworkRegistrationState(
                 2, 1, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId, 1,
-                false, false);
+                false, false, false);
         NetworkRegistrationState voiceResult = new NetworkRegistrationState(
                 1, 1, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId,
                 false, 0, 0, 0);
@@ -1826,7 +1826,7 @@
         testPhyChanBandwidthRatchetedOnPhyChanBandwidth();
         NetworkRegistrationState dataResult = new NetworkRegistrationState(
                 2, 1, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, 1, false,
-                false);
+                false, false);
         NetworkRegistrationState voiceResult = new NetworkRegistrationState(
                 1, 1, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null,
                 false, 0, 0, 0);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 8e124fa..61b0077 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -469,6 +469,54 @@
         assertNotEquals(groupId, newGroupId);
     }
 
+    @Test
+    @SmallTest
+    public void testDisabledSubscriptionGroup() throws Exception {
+        registerMockTelephonyRegistry();
+
+        testInsertSim();
+        // Adding a second profile and mark as embedded.
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
+
+        ContentValues values = new ContentValues();
+        values.put(SubscriptionManager.IS_EMBEDDED, 1);
+        values.put(SubscriptionManager.IS_OPPORTUNISTIC, 1);
+        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
+        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+
+        verify(mTelephonyRegisteryMock, times(1))
+                .notifyOpportunisticSubscriptionInfoChanged();
+
+        // Set sub 1 and 2 into same group.
+        int[] subIdList = new int[] {1, 2};
+        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertNotEquals(null, groupId);
+
+        verify(mTelephonyRegisteryMock, times(2))
+                .notifyOpportunisticSubscriptionInfoChanged();
+        List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
+                .getOpportunisticSubscriptions(mCallingPackage);
+        assertEquals(1, opptSubList.size());
+        assertEquals(2, opptSubList.get(0).getSubscriptionId());
+        assertEquals(false, opptSubList.get(0).isGroupDisabled());
+
+        // Unplug SIM 1. This should trigger subscription controller disabling sub 2.
+        values = new ContentValues();
+        values.put(SubscriptionManager.SIM_SLOT_INDEX, -1);
+        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null);
+        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+
+        verify(mTelephonyRegisteryMock, times(3))
+                .notifyOpportunisticSubscriptionInfoChanged();
+        opptSubList = mSubscriptionControllerUT.getOpportunisticSubscriptions(mCallingPackage);
+        assertEquals(1, opptSubList.size());
+        assertEquals(2, opptSubList.get(0).getSubscriptionId());
+        assertEquals(true, opptSubList.get(0).isGroupDisabled());
+    }
+
     private void registerMockTelephonyRegistry() {
         mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock);
         doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 0642e31..2517d22 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.anyInt;
@@ -61,6 +62,7 @@
 import com.android.internal.telephony.cdma.EriManager;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -104,6 +106,8 @@
     @Mock
     protected ServiceStateTracker mSST;
     @Mock
+    protected EmergencyNumberTracker mEmergencyNumberTracker;
+    @Mock
     protected GsmCdmaCallTracker mCT;
     @Mock
     protected ImsPhoneCallTracker mImsCT;
@@ -336,9 +340,13 @@
         mPackageManager = mContext.getPackageManager();
 
         //mTelephonyComponentFactory
+        doReturn(mTelephonyComponentFactory).when(mTelephonyComponentFactory).inject(anyString());
         doReturn(mSST).when(mTelephonyComponentFactory)
                 .makeServiceStateTracker(nullable(GsmCdmaPhone.class),
                         nullable(CommandsInterface.class));
+        doReturn(mEmergencyNumberTracker).when(mTelephonyComponentFactory)
+                .makeEmergencyNumberTracker(nullable(Phone.class),
+                        nullable(CommandsInterface.class));
         doReturn(mUiccProfile).when(mTelephonyComponentFactory)
                 .makeUiccProfile(nullable(Context.class), nullable(CommandsInterface.class),
                         nullable(IccCardStatus.class), anyInt(), nullable(UiccCard.class),
@@ -390,6 +398,7 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mPhone).getPhoneType();
         doReturn(mCT).when(mPhone).getCallTracker();
         doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mEmergencyNumberTracker).when(mPhone).getEmergencyNumberTracker();
         doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent();
         doReturn(mAppSmsManager).when(mPhone).getAppSmsManager();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index 1645af9..04e9305 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -98,8 +98,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class DcTrackerTest extends TelephonyTest {
 
@@ -132,6 +130,7 @@
     private static final Uri PREFERAPN_URI = Uri.parse(
             Telephony.Carriers.CONTENT_URI + "/preferapn");
     private static final int DATA_ENABLED_CHANGED = 0;
+    private static final String FAKE_PLMN = "44010";
 
     @Mock
     ISub mIsub;
@@ -208,20 +207,11 @@
             logd("   sortOrder = " + sortOrder);
 
             if (uri.compareTo(Telephony.Carriers.CONTENT_URI) == 0
-                    || uri.compareTo(Uri.withAppendedPath(
-                            Telephony.Carriers.CONTENT_URI, "filtered")) == 0) {
-                if (projection == null && selectionArgs == null && selection != null) {
+                    || uri.toString().startsWith(Uri.withAppendedPath(
+                            Telephony.Carriers.CONTENT_URI, "filtered").toString())) {
+                if (projection == null) {
 
-                    Pattern pattern = Pattern.compile("^numeric = '([0-9]*)'");
-                    Matcher matcher = pattern.matcher(selection);
-                    if (!matcher.find()) {
-                        logd("Cannot find MCC/MNC from " + selection);
-                        return null;
-                    }
-
-                    String plmn = matcher.group(1);
-
-                    logd("Query '" + plmn + "' APN settings");
+                    logd("Query '" + FAKE_PLMN + "' APN settings");
                     MatrixCursor mc = new MatrixCursor(
                             new String[]{Telephony.Carriers._ID, Telephony.Carriers.NUMERIC,
                                     Telephony.Carriers.NAME, Telephony.Carriers.APN,
@@ -248,7 +238,7 @@
 
                     mc.addRow(new Object[]{
                             2163,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "sp-mode",              // name
                             FAKE_APN1,              // apn
                             "",                     // proxy
@@ -280,7 +270,7 @@
 
                     mc.addRow(new Object[]{
                             2164,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "mopera U",             // name
                             FAKE_APN2,              // apn
                             "",                     // proxy
@@ -312,7 +302,7 @@
 
                     mc.addRow(new Object[]{
                             2165,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "b-mobile for Nexus",   // name
                             FAKE_APN3,              // apn
                             "",                     // proxy
@@ -344,7 +334,7 @@
 
                     mc.addRow(new Object[]{
                             2166,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "sp-mode ehrpd",        // name
                             FAKE_APN4,              // apn
                             "",                     // proxy
@@ -376,7 +366,7 @@
 
                     mc.addRow(new Object[]{
                             2166,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "b-mobile for Nexus",   // name
                             FAKE_APN5,              // apn
                             "",                     // proxy
@@ -437,7 +427,6 @@
         doReturn("fake.action_attached").when(mPhone).getActionAttached();
         doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mServiceState)
                 .getRilDataRadioTechnology();
-        doReturn("44010").when(mSimRecords).getOperatorNumeric();
 
         mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
                 sNetworkAttributes);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
new file mode 100644
index 0000000..11936f5
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.emergency;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.AsyncResult;
+import android.os.HandlerThread;
+import android.telephony.emergency.EmergencyNumber;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for EmergencyNumberTracker.java
+ */
+public class EmergencyNumberTrackerTest extends TelephonyTest {
+
+    private EmergencyNumberTracker mEmergencyNumberTrackerMock;
+    private List<EmergencyNumber> mEmergencyNumberListTestSample = new ArrayList<>();
+    private static final long TIMEOUT_MS = 500;
+
+    private class EmergencyNumberTrackerTestHandler extends HandlerThread {
+        private EmergencyNumberTrackerTestHandler(String name) {
+            super(name);
+        }
+        @Override
+        public void onLooperPrepared() {
+            mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+            mEmergencyNumberTrackerMock.DBG = true;
+            setReady(true);
+        }
+    }
+
+    private EmergencyNumberTrackerTestHandler mHandlerThread;
+
+    @Before
+    public void setUp() throws Exception {
+        logd("EmergencyNumberTrackerTest +Setup!");
+        super.setUp("EmergencyNumberTrackerTest");
+        initializeEmergencyNumberListTestSamples();
+        mHandlerThread = new EmergencyNumberTrackerTestHandler("EmergencyNumberTrackerTestHandler");
+        mHandlerThread.start();
+        waitUntilReady();
+        logd("EmergencyNumberTrackerTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+        super.tearDown();
+    }
+
+    private void initializeEmergencyNumberListTestSamples() {
+        EmergencyNumber emergencyNumberForTest = new EmergencyNumber("119", "jp",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+        mEmergencyNumberListTestSample.add(emergencyNumberForTest);
+    }
+
+    private void sendEmergencyNumberListFromRadio() {
+        mEmergencyNumberTrackerMock.sendMessage(
+                mEmergencyNumberTrackerMock.obtainMessage(
+                        1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
+                        new AsyncResult(null, mEmergencyNumberListTestSample, null)));
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+    }
+
+    @Test
+    public void testEmergencyNumberListFromRadio() throws Exception {
+        sendEmergencyNumberListFromRadio();
+        assertEquals(mEmergencyNumberListTestSample,
+                mEmergencyNumberTrackerMock.getRadioEmergencyNumberList());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
index df5552a..19906bd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
@@ -28,6 +28,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
+import android.telephony.emergency.EmergencyNumber;
 
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -361,6 +362,11 @@
     }
 
     @Override
+    public void notifyEmergencyNumberList(List<EmergencyNumber> emergencyNumberList) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
     public void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
             int backgroundCallState) {
         throw new RuntimeException("Not implemented");